Commit 33295fdb authored by bsazonov's avatar bsazonov Committed by Commit bot

Add progress and timeout dialogs for getting account management policy

This CL adds two dialogs that are shown by ConfirmSyncDataStateMachine.
Dialogs implementation is in ConfirmSyncDataStateMachineDelegate.
The first dialog is a progress dialog that is shown while Chrome requests
server for account management policy.
The second dialog is a timeout dialog that is shown if Chrome doesn't receive
account management policy within 30 seconds.

BUG=650121

Review-Url: https://codereview.chromium.org/2772203004
Cr-Commit-Position: refs/heads/master@{#460471}
parent 4e53ee16
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical">
<ProgressBar
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:indeterminate="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:text="@string/sign_in_getting_account_management_policy"
android:textSize="16sp"/>
</LinearLayout>
......@@ -8,11 +8,12 @@ import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.Context;
import android.os.Handler;
import android.support.annotation.IntDef;
import android.text.TextUtils;
import org.chromium.base.Callback;
import org.chromium.base.Promise;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.signin.ConfirmImportSyncDataDialog.ImportSyncType;
import java.lang.annotation.Retention;
......@@ -25,12 +26,13 @@ import java.lang.annotation.RetentionPolicy;
*
* This class progresses along the following state machine:
*
* E----\ G--\
* ^ | ^ |
* | | | v
* A->B->C->D-+->F->H
* | |
* \-------/
* E-----\ G--\
* ^ | ^ |
* | v | v
* A->B->C->D->+->F->H
* | ^
* v |
* \--------/
*
* Where:
* A - Start
......@@ -48,29 +50,31 @@ import java.lang.annotation.RetentionPolicy;
*/
public class ConfirmSyncDataStateMachine
implements ConfirmImportSyncDataDialog.Listener, ConfirmManagedSyncDataDialog.Listener {
@IntDef({
BEFORE_OLD_ACCOUNT_DIALOG, BEFORE_NEW_ACCOUNT_DIALOG,
AFTER_NEW_ACCOUNT_DIALOG, DONE
})
@Retention(RetentionPolicy.SOURCE)
@IntDef({BEFORE_OLD_ACCOUNT_DIALOG, BEFORE_NEW_ACCOUNT_DIALOG, AFTER_NEW_ACCOUNT_DIALOG, DONE})
private @interface State {}
private static final int BEFORE_OLD_ACCOUNT_DIALOG = 0; // Start of state B.
private static final int BEFORE_NEW_ACCOUNT_DIALOG = 1; // Start of state F.
private static final int AFTER_NEW_ACCOUNT_DIALOG = 2; // Start of state H.
private static final int DONE = 4;
private boolean mWipeData;
@State private int mState = BEFORE_OLD_ACCOUNT_DIALOG;
private static final int ACCOUNT_CHECK_TIMEOUT_MS = 30000;
private final ConfirmImportSyncDataDialog.Listener mCallback;
private final String mOldAccountName;
private final String mNewAccountName;
private final boolean mCurrentlyManaged;
private final Promise<Boolean> mNewAccountManaged = new Promise<>();
private final FragmentManager mFragmentManager;
private final Context mContext;
private final ImportSyncType mImportSyncType;
private final ConfirmSyncDataStateMachineDelegate mDelegate;
private final Handler mHandler = new Handler();
private boolean mWipeData;
private Boolean mNewAccountManaged;
private Runnable mCheckTimeoutRunnable;
/**
* Run this state machine, displaying the appropriate dialogs.
......@@ -113,6 +117,8 @@ public class ConfirmSyncDataStateMachine
private ConfirmSyncDataStateMachine(String oldAccountName, String newAccountName,
ImportSyncType importSyncType, FragmentManager fragmentManager, Context context,
ConfirmImportSyncDataDialog.Listener callback) {
ThreadUtils.assertOnUiThread();
mOldAccountName = oldAccountName;
mNewAccountName = newAccountName;
mImportSyncType = importSyncType;
......@@ -122,8 +128,11 @@ public class ConfirmSyncDataStateMachine
mCurrentlyManaged = SigninManager.get(context).getManagementDomain() != null;
// This check isn't needed right now, but can take a few seconds, so we kick it off early.
SigninManager.isUserManaged(mNewAccountName, mNewAccountManaged.fulfillmentCallback());
mDelegate = new ConfirmSyncDataStateMachineDelegate(mContext);
// New account management status isn't needed right now, but fetching it
// can take a few seconds, so we kick it off early.
requestNewAccountManagementStatus();
}
/**
......@@ -162,23 +171,13 @@ public class ConfirmSyncDataStateMachine
break;
case BEFORE_NEW_ACCOUNT_DIALOG:
mState = AFTER_NEW_ACCOUNT_DIALOG;
mNewAccountManaged.then(new Callback<Boolean>() {
@Override
public void onResult(Boolean newAccountManaged) {
if (newAccountManaged) {
// Show 'logging into managed account' dialog
// This will call back into onConfirm on success.
ConfirmManagedSyncDataDialog.showSignInToManagedAccountDialog(
ConfirmSyncDataStateMachine.this,
mFragmentManager, mContext.getResources(),
SigninManager.extractDomainName(mNewAccountName));
} else {
progress();
}
}
});
if (mNewAccountManaged != null) {
// No need to show dialog if account management status is already known
handleNewAccountManagementStatus();
} else {
showProgressDialog();
scheduleTimeout();
}
break;
case AFTER_NEW_ACCOUNT_DIALOG:
mState = DONE;
......@@ -189,6 +188,90 @@ public class ConfirmSyncDataStateMachine
}
}
private void requestNewAccountManagementStatus() {
SigninManager.isUserManaged(mNewAccountName, new Callback<Boolean>() {
@Override
public void onResult(Boolean result) {
setIsNewAccountManaged(result);
}
});
}
private void setIsNewAccountManaged(Boolean isManaged) {
assert isManaged != null;
mNewAccountManaged = isManaged;
if (mState == AFTER_NEW_ACCOUNT_DIALOG) {
cancelTimeout();
handleNewAccountManagementStatus();
}
}
private void handleNewAccountManagementStatus() {
assert mNewAccountManaged != null;
assert mState == AFTER_NEW_ACCOUNT_DIALOG;
mDelegate.dismissAllDialogs();
if (mNewAccountManaged) {
// Show 'logging into managed account' dialog
// This will call back into onConfirm on success.
ConfirmManagedSyncDataDialog.showSignInToManagedAccountDialog(
ConfirmSyncDataStateMachine.this, mFragmentManager, mContext.getResources(),
SigninManager.extractDomainName(mNewAccountName));
} else {
progress();
}
}
private void showProgressDialog() {
mDelegate.showFetchManagementPolicyProgressDialog(
new ConfirmSyncDataStateMachineDelegate.ProgressDialogListener() {
@Override
public void onCancel() {
ConfirmSyncDataStateMachine.this.onCancel();
}
});
}
private void scheduleTimeout() {
if (mCheckTimeoutRunnable == null) {
mCheckTimeoutRunnable = new Runnable() {
@Override
public void run() {
checkTimeout();
}
};
}
mHandler.postDelayed(mCheckTimeoutRunnable, ACCOUNT_CHECK_TIMEOUT_MS);
}
private void cancelTimeout() {
if (mCheckTimeoutRunnable == null) {
return;
}
mHandler.removeCallbacks(mCheckTimeoutRunnable);
mCheckTimeoutRunnable = null;
}
private void checkTimeout() {
assert mState == AFTER_NEW_ACCOUNT_DIALOG;
assert mNewAccountManaged == null;
mDelegate.showFetchManagementPolicyTimeoutDialog(
new ConfirmSyncDataStateMachineDelegate.TimeoutDialogListener() {
@Override
public void onCancel() {
ConfirmSyncDataStateMachine.this.onCancel();
}
@Override
public void onRetry() {
requestNewAccountManagementStatus();
scheduleTimeout();
}
});
}
// ConfirmImportSyncDataDialog.Listener implementation.
@Override
public void onConfirm(boolean wipeData) {
......@@ -205,6 +288,9 @@ public class ConfirmSyncDataStateMachine
// ConfirmImportSyncDataDialog.Listener & ConfirmManagedSyncDataDialog.Listener implementation.
@Override
public void onCancel() {
cancelTimeout();
mDelegate.dismissAllDialogs();
mState = DONE;
mCallback.onCancel();
}
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.signin;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import org.chromium.chrome.R;
/**
* Class to decouple ConfirmSyncDataStateMachine from UI code and dialog management.
*/
public class ConfirmSyncDataStateMachineDelegate {
/**
* Listener to receive events from progress dialog. If the dialog is not dismissed by showing
* other dialog or calling {@link ConfirmSyncDataStateMachineDelegate#dismissAllDialogs},
* then {@link #onCancel} will be called once.
*/
public interface ProgressDialogListener {
/**
* This method is called when user cancels the dialog in any way.
*/
void onCancel();
}
/**
* Listener to receive events from timeout dialog. If the dialog is not dismissed by showing
* other dialog or calling {@link ConfirmSyncDataStateMachineDelegate#dismissAllDialogs},
* then either {@link #onCancel} or {@link #onRetry} will be called once.
*/
public interface TimeoutDialogListener {
/**
* This method is called when user cancels the dialog in any way.
*/
void onCancel();
/**
* This method is called when user clicks retry button.
*/
void onRetry();
}
private final Context mContext;
private Dialog mProgressDialog;
private AlertDialog mTimeoutAlertDialog;
public ConfirmSyncDataStateMachineDelegate(Context context) {
mContext = context;
}
/**
* Shows progress dialog. Will dismiss other dialogs shown, if any.
*
* @param listener The {@link ProgressDialogListener} that will be notified about user actions.
*/
public void showFetchManagementPolicyProgressDialog(final ProgressDialogListener listener) {
dismissAllDialogs();
mProgressDialog = new AlertDialog.Builder(mContext, R.style.AlertDialogTheme)
.setView(R.layout.signin_progress_bar_dialog)
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int i) {
dialog.cancel();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
listener.onCancel();
}
})
.create();
mProgressDialog.show();
}
/**
* Shows timeout dialog. Will dismiss other dialogs shown, if any.
*
* @param listener The {@link TimeoutDialogListener} that will be notified about user actions.
*/
public void showFetchManagementPolicyTimeoutDialog(final TimeoutDialogListener listener) {
dismissAllDialogs();
mTimeoutAlertDialog =
new AlertDialog.Builder(mContext, R.style.AlertDialogTheme)
.setTitle(R.string.sign_in_timeout_title)
.setMessage(R.string.sign_in_timeout_message)
.setNegativeButton(R.string.cancel,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
})
.setPositiveButton(R.string.retry,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
listener.onRetry();
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
listener.onCancel();
}
})
.create();
mTimeoutAlertDialog.show();
}
/**
* Dismisses all dialogs.
*/
public void dismissAllDialogs() {
if (mProgressDialog != null) mProgressDialog.dismiss();
mProgressDialog = null;
if (mTimeoutAlertDialog != null) mTimeoutAlertDialog.dismiss();
mTimeoutAlertDialog = null;
}
}
......@@ -644,21 +644,13 @@ public class SigninManager implements AccountTrackerService.OnSystemAccountsSeed
/**
* Performs an asynchronous check to see if the user is a managed user.
* @param callback A callback to be called with true if the user is a managed user and false
* otherwise.
* otherwise. May be called synchronously from this function.
*/
public static void isUserManaged(String email, final Callback<Boolean> callback) {
if (nativeShouldLoadPolicyForUser(email)) {
nativeIsUserManaged(email, callback);
} else {
// Although we know the result immediately, the caller may not be able to handle the
// callback being executed during this method call. So we post the callback on the
// looper.
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
callback.onResult(false);
}
});
callback.onResult(false);
}
}
......
......@@ -215,6 +215,9 @@ CHAR-LIMIT guidelines:
<message name="IDS_COPIED" desc="Notification telling the user that something has been copied to the clipboard.">
Copied
</message>
<message name="IDS_RETRY" desc="The label for retry button. [CHAR-LIMIT=20]">
Retry
</message>
<!-- Main Preferences -->
<message name="IDS_PREFERENCES" desc="Title for Chrome's Settings.">
......@@ -1080,6 +1083,15 @@ To obtain new licenses, connect to the internet and play your downloaded content
<message name="IDS_SIGN_IN_BUTTON" desc="Label for a button to sign in">
Sign in
</message>
<message name="IDS_SIGN_IN_GETTING_ACCOUNT_MANAGEMENT_POLICY" desc="Title of progress bar dialog for getting management policy">
Contacting Google…this may take a minute
</message>
<message name="IDS_SIGN_IN_TIMEOUT_TITLE" desc="Title of sign in timeout alert dialog">
Can't sign in
</message>
<message name="IDS_SIGN_IN_TIMEOUT_MESSAGE" desc="Message of sign in timeout alert dialog">
Google took too long to respond
</message>
<!-- Sync strings -->
<message name="IDS_SIGN_OUT_MANAGED_ACCOUNT" desc="Title for prompts to sign out of managed accounts">
......
......@@ -928,6 +928,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/signin/ConfirmImportSyncDataDialog.java",
"java/src/org/chromium/chrome/browser/signin/ConfirmManagedSyncDataDialog.java",
"java/src/org/chromium/chrome/browser/signin/ConfirmSyncDataStateMachine.java",
"java/src/org/chromium/chrome/browser/signin/ConfirmSyncDataStateMachineDelegate.java",
"java/src/org/chromium/chrome/browser/signin/GoogleActivityController.java",
"java/src/org/chromium/chrome/browser/signin/OAuth2TokenService.java",
"java/src/org/chromium/chrome/browser/signin/SignOutDialogFragment.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment