- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / signin / SigninManager.java
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.signin;
6
7 import android.accounts.Account;
8 import android.app.Activity;
9 import android.app.AlertDialog;
10 import android.app.ProgressDialog;
11 import android.content.Context;
12 import android.content.DialogInterface;
13 import android.content.res.Resources;
14 import android.os.Handler;
15 import android.util.Log;
16
17 import org.chromium.base.CalledByNative;
18 import org.chromium.base.ObserverList;
19 import org.chromium.base.ThreadUtils;
20 import org.chromium.chrome.browser.invalidation.InvalidationController;
21 import org.chromium.chrome.browser.sync.ProfileSyncService;
22 import org.chromium.chrome.R;
23 import org.chromium.sync.internal_api.pub.base.ModelType;
24 import org.chromium.sync.notifier.SyncStatusHelper;
25 import org.chromium.sync.signin.ChromeSigninController;
26
27 import java.util.HashSet;
28
29 /**
30  * Android wrapper of the SigninManager which provides access from the Java layer.
31  * <p/>
32  * This class handles common paths during the sign-in and sign-out flows.
33  * <p/>
34  * Only usable from the UI thread as the native SigninManager requires its access to be in the
35  * UI thread.
36  * <p/>
37  * See chrome/browser/signin/signin_manager_android.h for more details.
38  */
39 public class SigninManager {
40
41     private static final String TAG = "SigninManager";
42
43     private static SigninManager sSigninManager;
44
45     private final Context mContext;
46     private final int mNativeSigninManagerAndroid;
47
48     /** Tracks whether the First Run check has been completed.
49      *
50      * A new sign-in can not be started while this is pending, to prevent the
51      * pending check from eventually starting a 2nd sign-in.
52      */
53     private boolean mFirstRunCheckIsPending = true;
54     private ObserverList<SignInAllowedObserver> mSignInAllowedObservers =
55             new ObserverList<SignInAllowedObserver>();
56
57     private Activity mSignInActivity;
58     private Account mSignInAccount;
59     private Observer mSignInObserver;
60     private boolean mPassive = false;
61
62     private ProgressDialog mSignOutProgressDialog;
63     private Runnable mSignOutCallback;
64
65     private AlertDialog mPolicyConfirmationDialog;
66
67     /**
68      * SignInAllowedObservers will be notified once signing-in becomes allowed or disallowed.
69      */
70     public static interface SignInAllowedObserver {
71         /**
72          * Invoked once all startup checks are done and signing-in becomes allowed, or disallowed.
73          */
74         public void onSignInAllowedChanged();
75     }
76
77     /**
78      * The Observer of startSignIn() will be notified when sign-in completes.
79      */
80     public static interface Observer {
81         /**
82          * Invoked after sign-in completed successfully.
83          */
84         public void onSigninComplete();
85
86         /**
87          * Invoked when the sign-in process was cancelled by the user.
88          *
89          * The user should have the option of going back and starting the process again,
90          * if possible.
91          */
92         public void onSigninCancelled();
93     }
94
95     /**
96      * A helper method for retrieving the application-wide SigninManager.
97      * <p/>
98      * Can only be accessed on the main thread.
99      *
100      * @param context the ApplicationContext is retrieved from the context used as an argument.
101      * @return a singleton instance of the SigninManager.
102      */
103     public static SigninManager get(Context context) {
104         ThreadUtils.assertOnUiThread();
105         if (sSigninManager == null) {
106             sSigninManager = new SigninManager(context);
107         }
108         return sSigninManager;
109     }
110
111     private SigninManager(Context context) {
112         ThreadUtils.assertOnUiThread();
113         mContext = context.getApplicationContext();
114         mNativeSigninManagerAndroid = nativeInit();
115     }
116
117     /**
118      * Notifies the SigninManager that the First Run check has completed.
119      *
120      * The user will be allowed to sign-in once this is signaled.
121      */
122     public void onFirstRunCheckDone() {
123         mFirstRunCheckIsPending = false;
124
125         if (isSignInAllowed()) {
126             notifySignInAllowedChanged();
127         }
128     }
129
130     /**
131      * Returns true if signin can be started now.
132      */
133     public boolean isSignInAllowed() {
134         return !mFirstRunCheckIsPending &&
135                 mSignInAccount == null &&
136                 ChromeSigninController.get(mContext).getSignedInUser() == null;
137     }
138
139     public void addSignInAllowedObserver(SignInAllowedObserver observer) {
140         mSignInAllowedObservers.addObserver(observer);
141     }
142
143     public void removeSignInAllowedObserver(SignInAllowedObserver observer) {
144         mSignInAllowedObservers.removeObserver(observer);
145     }
146
147     private void notifySignInAllowedChanged() {
148         new Handler().post(new Runnable() {
149             @Override
150             public void run() {
151                 for (SignInAllowedObserver observer : mSignInAllowedObservers) {
152                     observer.onSignInAllowedChanged();
153                 }
154             }
155         });
156     }
157
158     /**
159      * Starts the sign-in flow, and executes the callback when ready to proceed.
160      * <p/>
161      * This method checks with the native side whether the account has management enabled, and may
162      * present a dialog to the user to confirm sign-in. The callback is invoked once these processes
163      * and the common sign-in initialization complete.
164      *
165      * @param activity The context to use for the operation.
166      * @param account The account to sign in to.
167      * @param passive If passive is true then this operation should not interact with the user.
168      * @param callback The Observer to notify when the sign-in process is finished.
169      */
170     public void startSignIn(
171             Activity activity, final Account account, boolean passive, final Observer observer) {
172         assert mSignInActivity == null;
173         assert mSignInAccount == null;
174         assert mSignInObserver == null;
175
176         if (mFirstRunCheckIsPending) {
177             Log.w(TAG, "Ignoring sign-in request until the First Run check completes.");
178             return;
179         }
180
181         mSignInActivity = activity;
182         mSignInAccount = account;
183         mSignInObserver = observer;
184         mPassive = passive;
185
186         notifySignInAllowedChanged();
187
188         if (!nativeShouldLoadPolicyForUser(account.name)) {
189             // Proceed with the sign-in flow without checking for policy if it can be determined
190             // that this account can't have management enabled based on the username.
191             doSignIn();
192             return;
193         }
194
195         Log.d(TAG, "Checking if account has policy management enabled");
196         // This will call back to onPolicyCheckedBeforeSignIn.
197         nativeCheckPolicyBeforeSignIn(mNativeSigninManagerAndroid, account.name);
198     }
199
200     @CalledByNative
201     private void onPolicyCheckedBeforeSignIn(String managementDomain) {
202         if (managementDomain == null) {
203             Log.d(TAG, "Account doesn't have policy");
204             doSignIn();
205             return;
206         }
207
208         if (mSignInActivity.isDestroyed()) {
209             // The activity is no longer running, cancel sign in.
210             cancelSignIn();
211             return;
212         }
213
214         if (mPassive) {
215             // If this is a passive interaction (e.g. auto signin) then don't show the confirmation
216             // dialog.
217             nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
218             return;
219         }
220
221         Log.d(TAG, "Account has policy management");
222         AlertDialog.Builder builder = new AlertDialog.Builder(mSignInActivity);
223         builder.setTitle(R.string.policy_dialog_title);
224         builder.setMessage(mContext.getResources().getString(R.string.policy_dialog_message,
225                                                              managementDomain));
226         builder.setPositiveButton(
227                 R.string.policy_dialog_proceed,
228                 new DialogInterface.OnClickListener() {
229                     @Override
230                     public void onClick(DialogInterface dialog, int id) {
231                         Log.d(TAG, "Accepted policy management, proceeding with sign-in");
232                         // This will call back to onPolicyFetchedBeforeSignIn.
233                         nativeFetchPolicyBeforeSignIn(mNativeSigninManagerAndroid);
234                         mPolicyConfirmationDialog = null;
235                     }
236                 });
237         builder.setNegativeButton(
238                 R.string.policy_dialog_cancel,
239                 new DialogInterface.OnClickListener() {
240                     @Override
241                     public void onClick(DialogInterface dialog, int id) {
242                         Log.d(TAG, "Cancelled sign-in");
243                         cancelSignIn();
244                         mPolicyConfirmationDialog = null;
245                     }
246                 });
247         builder.setOnDismissListener(
248                 new DialogInterface.OnDismissListener() {
249                     @Override
250                     public void onDismiss(DialogInterface dialog) {
251                         if (mPolicyConfirmationDialog != null) {
252                             Log.d(TAG, "Policy dialog dismissed, cancelling sign-in.");
253                             cancelSignIn();
254                             mPolicyConfirmationDialog = null;
255                         }
256                     }
257                 });
258         mPolicyConfirmationDialog = builder.create();
259         mPolicyConfirmationDialog.show();
260     }
261
262     @CalledByNative
263     private void onPolicyFetchedBeforeSignIn() {
264         // Policy has been fetched for the user and is being enforced; features like sync may now
265         // be disabled by policy, and the rest of the sign-in flow can be resumed.
266         doSignIn();
267     }
268
269     private void doSignIn() {
270         Log.d(TAG, "Committing the sign-in process now");
271         assert mSignInAccount != null;
272
273         // Cache the signed-in account name.
274         ChromeSigninController.get(mContext).setSignedInAccountName(mSignInAccount.name);
275
276         // Tell the native side that sign-in has completed.
277         // This will trigger NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL.
278         nativeOnSignInCompleted(mNativeSigninManagerAndroid, mSignInAccount.name);
279
280         // Register for invalidations.
281         InvalidationController invalidationController = InvalidationController.get(mContext);
282         invalidationController.setRegisteredTypes(mSignInAccount, true, new HashSet<ModelType>());
283
284         // Sign-in to sync.
285         ProfileSyncService profileSyncService = ProfileSyncService.get(mContext);
286         if (SyncStatusHelper.get(mContext).isSyncEnabled(mSignInAccount) &&
287                 !profileSyncService.hasSyncSetupCompleted()) {
288             profileSyncService.setSetupInProgress(true);
289             profileSyncService.syncSignIn();
290         }
291
292         if (mSignInObserver != null)
293             mSignInObserver.onSigninComplete();
294
295         // All done, cleanup.
296         Log.d(TAG, "Signin done");
297         mSignInActivity = null;
298         mSignInAccount = null;
299         mSignInObserver = null;
300         notifySignInAllowedChanged();
301     }
302
303     /**
304      * Signs out of Chrome.
305      * <p/>
306      * This method clears the signed-in username, stops sync and sends out a
307      * sign-out notification on the native side.
308      *
309      * @param activity If not null then a progress dialog is shown over the activity until signout
310      * completes, in case the account had management enabled. The activity must be valid until the
311      * callback is invoked.
312      * @param callback Will be invoked after signout completes, if not null.
313      */
314     public void signOut(Activity activity, Runnable callback) {
315         mSignOutCallback = callback;
316
317         boolean wipeData = getManagementDomain() != null;
318         Log.d(TAG, "Signing out, wipe data? " + wipeData);
319
320         ChromeSigninController.get(mContext).clearSignedInUser();
321         ProfileSyncService.get(mContext).signOut();
322         nativeSignOut(mNativeSigninManagerAndroid);
323
324         if (wipeData) {
325             wipeProfileData(activity);
326         } else {
327             onSignOutDone();
328         }
329     }
330
331     /**
332      * Returns the management domain if the signed in account is managed, otherwise returns null.
333      */
334     public String getManagementDomain() {
335         return nativeGetManagementDomain(mNativeSigninManagerAndroid);
336     }
337
338     public void logInSignedInUser() {
339         nativeLogInSignedInUser(mNativeSigninManagerAndroid);
340     }
341
342     private void cancelSignIn() {
343         if (mSignInObserver != null)
344             mSignInObserver.onSigninCancelled();
345         mSignInActivity = null;
346         mSignInObserver = null;
347         mSignInAccount = null;
348         notifySignInAllowedChanged();
349     }
350
351     private void wipeProfileData(Activity activity) {
352         if (activity != null) {
353             // We don't have progress update, so this takes an indeterminate amount of time.
354             boolean indeterminate = true;
355             // This dialog is not cancelable by the user.
356             boolean cancelable = false;
357             mSignOutProgressDialog = ProgressDialog.show(
358                 activity,
359                 activity.getString(R.string.wiping_profile_data_title),
360                 activity.getString(R.string.wiping_profile_data_message),
361                 indeterminate, cancelable);
362         }
363         // This will call back to onProfileDataWiped().
364         nativeWipeProfileData(mNativeSigninManagerAndroid);
365     }
366
367     @CalledByNative
368     private void onProfileDataWiped() {
369         if (mSignOutProgressDialog != null && mSignOutProgressDialog.isShowing())
370             mSignOutProgressDialog.dismiss();
371         onSignOutDone();
372     }
373
374     private void onSignOutDone() {
375         if (mSignOutCallback != null) {
376             new Handler().post(mSignOutCallback);
377             mSignOutCallback = null;
378         }
379     }
380
381     // Native methods.
382     private native int nativeInit();
383     private native boolean nativeShouldLoadPolicyForUser(String username);
384     private native void nativeCheckPolicyBeforeSignIn(
385             int nativeSigninManagerAndroid, String username);
386     private native void nativeFetchPolicyBeforeSignIn(int nativeSigninManagerAndroid);
387     private native void nativeOnSignInCompleted(int nativeSigninManagerAndroid, String username);
388     private native void nativeSignOut(int nativeSigninManagerAndroid);
389     private native String nativeGetManagementDomain(int nativeSigninManagerAndroid);
390     private native void nativeWipeProfileData(int nativeSigninManagerAndroid);
391     private native void nativeLogInSignedInUser(int nativeSigninManagerAndroid);
392 }