replicant-packages_apps_Email/src/com/android/email/activity/setup/AccountSetupFinal.java

1305 lines
55 KiB
Java

/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity.setup;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.app.ActionBar;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.LoaderManager;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.CursorLoader;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import com.android.email.R;
import com.android.email.setup.AuthenticatorSetupIntentHelper;
import com.android.email.service.EmailServiceUtils;
import com.android.emailcommon.VendorPolicyLoader;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.HostAuth;
import com.android.emailcommon.service.SyncWindow;
import com.android.mail.analytics.Analytics;
import com.android.mail.providers.MailAppProvider;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogUtils;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
public class AccountSetupFinal extends AccountSetupActivity
implements AccountFinalizeFragment.Callback,
AccountSetupNoteDialogFragment.Callback, AccountCreationFragment.Callback,
AccountCheckSettingsFragment.Callback, SecurityRequiredDialogFragment.Callback,
CheckSettingsErrorDialogFragment.Callback, CheckSettingsProgressDialogFragment.Callback,
AccountSetupTypeFragment.Callback, AccountSetupNamesFragment.Callback,
AccountSetupOptionsFragment.Callback, AccountSetupBasicsFragment.Callback,
AccountServerBaseFragment.Callback, AccountSetupCredentialsFragment.Callback,
DuplicateAccountDialogFragment.Callback, AccountSetupABFragment.Callback {
/**
* Direct access for forcing account creation
* For use by continuous automated test system (e.g. in conjunction with monkey tests)
*
* === Support for automated testing ==
* This activity can also be launched directly via INTENT_FORCE_CREATE_ACCOUNT. This is intended
* only for use by continuous test systems, and is currently only available when
* {@link ActivityManager#isRunningInTestHarness()} is set. To use this mode, you must
* construct an intent which contains all necessary information to create the account. No
* connection checking is done, so the account may or may not actually work. Here is a sample
* command, for a gmail account "test_account" with a password of "test_password".
*
* $ adb shell am start -a com.android.email.FORCE_CREATE_ACCOUNT \
* -e EMAIL test_account@gmail.com \
* -e USER "Test Account Name" \
* -e INCOMING imap+ssl+://test_account:test_password@imap.gmail.com \
* -e OUTGOING smtp+ssl+://test_account:test_password@smtp.gmail.com
*
* Note: For accounts that require the full email address in the login, encode the @ as %40.
* Note: Exchange accounts that require device security policies cannot be created
* automatically.
*
* For accounts that correspond to services in providers.xml you can also use the following form
*
* $adb shell am start -a com.android.email.FORCE_CREATE_ACCOUNT \
* -e EMAIL test_account@gmail.com \
* -e PASSWORD test_password
*
* and the appropriate incoming/outgoing information will be filled in automatically.
*/
private static String INTENT_FORCE_CREATE_ACCOUNT;
private static final String EXTRA_CREATE_ACCOUNT_EMAIL = "EMAIL";
private static final String EXTRA_CREATE_ACCOUNT_USER = "USER";
private static final String EXTRA_CREATE_ACCOUNT_PASSWORD = "PASSWORD";
private static final String EXTRA_CREATE_ACCOUNT_INCOMING = "INCOMING";
private static final String EXTRA_CREATE_ACCOUNT_OUTGOING = "OUTGOING";
private static final String EXTRA_CREATE_ACCOUNT_SYNC_LOOKBACK = "SYNC_LOOKBACK";
private static final String CREATE_ACCOUNT_SYNC_ALL_VALUE = "ALL";
private static final Boolean DEBUG_ALLOW_NON_TEST_HARNESS_CREATION = false;
protected static final String ACTION_JUMP_TO_INCOMING = "jumpToIncoming";
protected static final String ACTION_JUMP_TO_OUTGOING = "jumpToOutgoing";
protected static final String ACTION_JUMP_TO_OPTIONS = "jumpToOptions";
private static final String SAVESTATE_KEY_IS_PROCESSING = "AccountSetupFinal.is_processing";
private static final String SAVESTATE_KEY_STATE = "AccountSetupFinal.state";
private static final String SAVESTATE_KEY_PROVIDER = "AccountSetupFinal.provider";
private static final String SAVESTATE_KEY_AUTHENTICATOR_RESPONSE = "AccountSetupFinal.authResp";
private static final String SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR =
"AccountSetupFinal.authErr";
private static final String SAVESTATE_KEY_IS_PRE_CONFIGURED = "AccountSetupFinal.preconfig";
private static final String SAVESTATE_KEY_SKIP_AUTO_DISCOVER = "AccountSetupFinal.noAuto";
private static final String SAVESTATE_KEY_PASSWORD_FAILED = "AccountSetupFinal.passwordFailed";
private static final String CONTENT_FRAGMENT_TAG = "AccountSetupContentFragment";
private static final String CREDENTIALS_BACKSTACK_TAG = "AccountSetupCredentialsFragment";
// Collecting initial email and password
private static final int STATE_BASICS = 0;
// Show the user some interstitial message after email entry
private static final int STATE_BASICS_POST = 1;
// Account is not pre-configured, query user for account type
private static final int STATE_TYPE = 2;
// Account is pre-configured, but the user picked a different protocol
private static final int STATE_AB = 3;
// Collect initial password or oauth token
private static final int STATE_CREDENTIALS = 4;
// Account is a pre-configured account, run the checker
private static final int STATE_CHECKING_PRECONFIGURED = 5;
// Auto-discovering exchange account info, possibly other protocols later
private static final int STATE_AUTO_DISCOVER = 6;
// User is entering incoming settings
private static final int STATE_MANUAL_INCOMING = 7;
// We're checking incoming settings
private static final int STATE_CHECKING_INCOMING = 8;
// User is entering outgoing settings
private static final int STATE_MANUAL_OUTGOING = 9;
// We're checking outgoing settings
private static final int STATE_CHECKING_OUTGOING = 10;
// User is entering sync options
private static final int STATE_OPTIONS = 11;
// We're creating the account
private static final int STATE_CREATING = 12;
// User is entering account name and real name
private static final int STATE_NAMES = 13;
// we're finalizing the account
private static final int STATE_FINALIZE = 14;
private int mState = STATE_BASICS;
private boolean mIsProcessing = false;
private boolean mForceCreate = false;
private boolean mReportAccountAuthenticatorError;
private AccountAuthenticatorResponse mAccountAuthenticatorResponse;
// True if this provider is found in our providers.xml, set after Basics
private boolean mIsPreConfiguredProvider = false;
// True if the user selected manual setup
private boolean mSkipAutoDiscover = false;
// True if validating the pre-configured provider failed and we want manual setup
private boolean mPreConfiguredFailed = false;
private VendorPolicyLoader.Provider mProvider;
private boolean mPasswordFailed;
private static final int OWNER_NAME_LOADER_ID = 0;
private String mOwnerName;
private static final int EXISTING_ACCOUNTS_LOADER_ID = 1;
private Map<String, String> mExistingAccountsMap;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
final String action = intent.getAction();
if (INTENT_FORCE_CREATE_ACCOUNT == null) {
INTENT_FORCE_CREATE_ACCOUNT = getString(R.string.intent_force_create_email_account);
}
setContentView(R.layout.account_setup_activity);
final ActionBar actionBar = getActionBar();
if (actionBar != null) {
// Hide the app icon.
actionBar.setIcon(android.R.color.transparent);
actionBar.setDisplayUseLogoEnabled(false);
}
if (savedInstanceState != null) {
mIsProcessing = savedInstanceState.getBoolean(SAVESTATE_KEY_IS_PROCESSING, false);
mState = savedInstanceState.getInt(SAVESTATE_KEY_STATE, STATE_OPTIONS);
mProvider = (VendorPolicyLoader.Provider)
savedInstanceState.getSerializable(SAVESTATE_KEY_PROVIDER);
mAccountAuthenticatorResponse =
savedInstanceState.getParcelable(SAVESTATE_KEY_AUTHENTICATOR_RESPONSE);
mReportAccountAuthenticatorError =
savedInstanceState.getBoolean(SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR);
mIsPreConfiguredProvider =
savedInstanceState.getBoolean(SAVESTATE_KEY_IS_PRE_CONFIGURED);
mSkipAutoDiscover = savedInstanceState.getBoolean(SAVESTATE_KEY_SKIP_AUTO_DISCOVER);
mPasswordFailed = savedInstanceState.getBoolean(SAVESTATE_KEY_PASSWORD_FAILED);
} else {
// If we're not restoring from a previous state, we want to configure the initial screen
// Set aside incoming AccountAuthenticatorResponse, if there was any
mAccountAuthenticatorResponse = getIntent()
.getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
if (mAccountAuthenticatorResponse != null) {
// When this Activity is called as part of account authentification flow,
// we are responsible for eventually reporting the result (success or failure) to
// the account manager. Most exit paths represent an failed or abandoned setup,
// so the default is to report the error. Success will be reported by the code in
// AccountSetupOptions that commits the finally created account.
mReportAccountAuthenticatorError = true;
}
// Initialize the SetupDataFragment
if (INTENT_FORCE_CREATE_ACCOUNT.equals(action)) {
mSetupData.setFlowMode(AuthenticatorSetupIntentHelper.FLOW_MODE_FORCE_CREATE);
} else {
final int intentFlowMode = intent.getIntExtra(
AuthenticatorSetupIntentHelper.EXTRA_FLOW_MODE,
AuthenticatorSetupIntentHelper.FLOW_MODE_UNSPECIFIED);
final String flowAccountType = intent.getStringExtra(
AuthenticatorSetupIntentHelper.EXTRA_FLOW_ACCOUNT_TYPE);
mSetupData.setAmProtocol(
EmailServiceUtils.getProtocolFromAccountType(this, flowAccountType));
mSetupData.setFlowMode(intentFlowMode);
}
mState = STATE_BASICS;
// Support unit testing individual screens
if (TextUtils.equals(ACTION_JUMP_TO_INCOMING, action)) {
mState = STATE_MANUAL_INCOMING;
} else if (TextUtils.equals(ACTION_JUMP_TO_OUTGOING, action)) {
mState = STATE_MANUAL_OUTGOING;
} else if (TextUtils.equals(ACTION_JUMP_TO_OPTIONS, action)) {
mState = STATE_OPTIONS;
}
updateContentFragment(false /* addToBackstack */);
mPasswordFailed = false;
}
if (!mIsProcessing && mSetupData.getFlowMode() ==
AuthenticatorSetupIntentHelper.FLOW_MODE_FORCE_CREATE) {
/**
* To support continuous testing, we allow the forced creation of accounts.
* This works in a manner fairly similar to automatic setup, in which the complete
* server Uri's are available, except that we will also skip checking (as if both
* checks were true) and all other UI.
*
* email: The email address for the new account
* user: The user name for the new account
* incoming: The URI-style string defining the incoming account
* outgoing: The URI-style string defining the outgoing account
*/
final String email = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_EMAIL);
final String user = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_USER);
final String password = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_PASSWORD);
final String incoming = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_INCOMING);
final String outgoing = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_OUTGOING);
final String syncLookbackText = intent.getStringExtra(EXTRA_CREATE_ACCOUNT_SYNC_LOOKBACK);
final int syncLookback;
if (TextUtils.equals(syncLookbackText, CREATE_ACCOUNT_SYNC_ALL_VALUE)) {
syncLookback = SyncWindow.SYNC_WINDOW_ALL;
} else {
syncLookback = -1;
}
// If we've been explicitly provided with all the details to fill in the account, we
// can use them
final boolean explicitForm = !(TextUtils.isEmpty(user) ||
TextUtils.isEmpty(incoming) || TextUtils.isEmpty(outgoing));
// If we haven't been provided the details, but we have the password, we can look up
// the info from providers.xml
final boolean implicitForm = !TextUtils.isEmpty(password) && !explicitForm;
if (TextUtils.isEmpty(email) || !(explicitForm || implicitForm)) {
LogUtils.e(LogUtils.TAG, "Force account create requires extras EMAIL, " +
"USER, INCOMING, OUTGOING, or EMAIL and PASSWORD");
finish();
return;
}
if (implicitForm) {
final String[] emailParts = email.split("@");
final String domain = emailParts[1].trim();
mProvider = AccountSettingsUtils.findProviderForDomain(this, domain);
if (mProvider == null) {
LogUtils.e(LogUtils.TAG, "findProviderForDomain couldn't find provider");
finish();
return;
}
mIsPreConfiguredProvider = true;
mSetupData.setEmail(email);
boolean autoSetupCompleted = finishAutoSetup();
if (!autoSetupCompleted) {
LogUtils.e(LogUtils.TAG, "Force create account failed to create account");
finish();
return;
}
final Account account = mSetupData.getAccount();
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
recvAuth.mPassword = password;
final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
sendAuth.mPassword = password;
} else {
final Account account = mSetupData.getAccount();
try {
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
recvAuth.setHostAuthFromString(incoming);
final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
sendAuth.setHostAuthFromString(outgoing);
} catch (URISyntaxException e) {
// If we can't set up the URL, don't continue
Toast.makeText(this, R.string.account_setup_username_password_toast,
Toast.LENGTH_LONG)
.show();
finish();
return;
}
populateSetupData(user, email);
// We need to do this after calling populateSetupData(), because that will
// overwrite it with the default values.
if (syncLookback >= SyncWindow.SYNC_WINDOW_ACCOUNT &&
syncLookback <= SyncWindow.SYNC_WINDOW_ALL) {
account.mSyncLookback = syncLookback;
}
}
mState = STATE_OPTIONS;
updateContentFragment(false /* addToBackstack */);
getFragmentManager().executePendingTransactions();
if (!DEBUG_ALLOW_NON_TEST_HARNESS_CREATION &&
!ActivityManager.isRunningInTestHarness()) {
LogUtils.e(LogUtils.TAG,
"ERROR: Force account create only allowed while in test harness");
finish();
return;
}
mForceCreate = true;
}
// Launch a loader to look up the owner name. It should be ready well in advance of
// the time the user clicks next or manual.
getLoaderManager().initLoader(OWNER_NAME_LOADER_ID, null, new OwnerNameLoaderCallbacks());
// Launch a loader to cache some info about existing accounts so we can dupe-check against
// them.
getLoaderManager().initLoader(EXISTING_ACCOUNTS_LOADER_ID, null,
new ExistingAccountsLoaderCallbacks());
}
private class OwnerNameLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
return new CursorLoader(AccountSetupFinal.this,
ContactsContract.Profile.CONTENT_URI,
new String[] {ContactsContract.Profile.DISPLAY_NAME_PRIMARY},
null, null, null);
}
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
if (data != null && data.moveToFirst()) {
mOwnerName = data.getString(data.getColumnIndex(
ContactsContract.Profile.DISPLAY_NAME_PRIMARY));
}
}
@Override
public void onLoaderReset(final Loader<Cursor> loader) {}
}
private class ExistingAccountsLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
return new CursorLoader(AccountSetupFinal.this, MailAppProvider.getAccountsUri(),
new String[] {UIProvider.AccountColumns.ACCOUNT_MANAGER_NAME,
UIProvider.AccountColumns.NAME},
null, null, null);
}
@Override
public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
if (data == null || !data.moveToFirst()) {
mExistingAccountsMap = null;
return;
}
mExistingAccountsMap = new HashMap<>();
final int emailColumnIndex = data.getColumnIndex(
UIProvider.AccountColumns.ACCOUNT_MANAGER_NAME);
final int displayNameColumnIndex =
data.getColumnIndex(UIProvider.AccountColumns.NAME);
do {
final String email = data.getString(emailColumnIndex);
final String displayName = data.getString(displayNameColumnIndex);
mExistingAccountsMap.put(email,
TextUtils.isEmpty(displayName) ? email : displayName);
} while (data.moveToNext());
}
@Override
public void onLoaderReset(final Loader<Cursor> loader) {
mExistingAccountsMap = null;
}
}
@Override
protected void onStart() {
super.onStart();
Analytics.getInstance().activityStart(this);
}
@Override
protected void onResume() {
super.onResume();
if (mForceCreate) {
mForceCreate = false;
// We need to do this after onCreate so that we can ensure that the fragment is
// fully created before querying it.
// This will call initiateAccountCreation() for us
proceed();
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(SAVESTATE_KEY_IS_PROCESSING, mIsProcessing);
outState.putInt(SAVESTATE_KEY_STATE, mState);
outState.putSerializable(SAVESTATE_KEY_PROVIDER, mProvider);
outState.putParcelable(SAVESTATE_KEY_AUTHENTICATOR_RESPONSE, mAccountAuthenticatorResponse);
outState.putBoolean(SAVESTATE_KEY_REPORT_AUTHENTICATOR_ERROR,
mReportAccountAuthenticatorError);
outState.putBoolean(SAVESTATE_KEY_IS_PRE_CONFIGURED, mIsPreConfiguredProvider);
outState.putBoolean(SAVESTATE_KEY_PASSWORD_FAILED, mPasswordFailed);
}
@Override
protected void onStop() {
super.onStop();
Analytics.getInstance().activityStop(this);
}
/**
* Swap in the new fragment according to mState. This pushes the current fragment onto the back
* stack, so only call it once per transition.
*/
private void updateContentFragment(boolean addToBackstack) {
final AccountSetupFragment f;
String backstackTag = null;
switch (mState) {
case STATE_BASICS:
f = AccountSetupBasicsFragment.newInstance();
break;
case STATE_TYPE:
f = AccountSetupTypeFragment.newInstance();
break;
case STATE_AB:
f = AccountSetupABFragment.newInstance(mSetupData.getEmail(),
mSetupData.getAmProtocol(), mSetupData.getIncomingProtocol(this));
break;
case STATE_CREDENTIALS:
f = AccountSetupCredentialsFragment.newInstance(mSetupData.getEmail(),
mSetupData.getIncomingProtocol(this), mSetupData.getClientCert(this),
mPasswordFailed, false /* standalone */);
backstackTag = CREDENTIALS_BACKSTACK_TAG;
break;
case STATE_MANUAL_INCOMING:
f = AccountSetupIncomingFragment.newInstance(false);
break;
case STATE_MANUAL_OUTGOING:
f = AccountSetupOutgoingFragment.newInstance(false);
break;
case STATE_OPTIONS:
f = AccountSetupOptionsFragment.newInstance();
break;
case STATE_NAMES:
f = AccountSetupNamesFragment.newInstance();
break;
default:
throw new IllegalStateException("Incorrect state " + mState);
}
f.setState(mState);
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
ft.replace(R.id.setup_fragment_container, f, CONTENT_FRAGMENT_TAG);
if (addToBackstack) {
ft.addToBackStack(backstackTag);
}
ft.commit();
final InputMethodManager imm =
(InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
final View fragment_container = findViewById(R.id.setup_fragment_container);
imm.hideSoftInputFromWindow(fragment_container.getWindowToken(),
0 /* flags: always hide */);
}
/**
* Retrieve the current content fragment
* @return The content fragment or null if it wasn't found for some reason
*/
private AccountSetupFragment getContentFragment() {
return (AccountSetupFragment) getFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG);
}
/**
* Reads the flow state saved into the current fragment and restores mState to it, also
* resetting the headline at the same time.
*/
private void resetStateFromCurrentFragment() {
AccountSetupFragment f = getContentFragment();
mState = f.getState();
}
/**
* Main choreography function to handle moving forward through scenes. Moving back should be
* generally handled for us by the back stack
*/
protected void proceed() {
mIsProcessing = false;
final AccountSetupFragment oldContentFragment = getContentFragment();
if (oldContentFragment != null) {
oldContentFragment.setNextButtonEnabled(true);
}
getFragmentManager().executePendingTransactions();
switch (mState) {
case STATE_BASICS:
final boolean advance = onBasicsComplete();
if (!advance) {
mState = STATE_BASICS_POST;
break;
} // else fall through
case STATE_BASICS_POST:
if (shouldDivertToManual()) {
mSkipAutoDiscover = true;
mIsPreConfiguredProvider = false;
mState = STATE_TYPE;
} else {
mSkipAutoDiscover = false;
if (mIsPreConfiguredProvider) {
if (!TextUtils.isEmpty(mSetupData.getAmProtocol()) &&
!TextUtils.equals(mSetupData.getAmProtocol(),
mSetupData.getIncomingProtocol(this))) {
mState = STATE_AB;
} else {
mState = STATE_CREDENTIALS;
if (possiblyDivertToGmail()) {
return;
}
}
} else {
final String amProtocol = mSetupData.getAmProtocol();
if (!TextUtils.isEmpty(amProtocol)) {
mSetupData.setIncomingProtocol(this, amProtocol);
final Account account = mSetupData.getAccount();
setDefaultsForProtocol(account);
mState = STATE_CREDENTIALS;
} else {
mState = STATE_TYPE;
}
}
}
updateContentFragment(true /* addToBackstack */);
break;
case STATE_TYPE:
// We either got here through "Manual Setup" or because we didn't find the provider
mState = STATE_CREDENTIALS;
updateContentFragment(true /* addToBackstack */);
break;
case STATE_AB:
if (possiblyDivertToGmail()) {
return;
}
mState = STATE_CREDENTIALS;
updateContentFragment(true /* addToBackstack */);
break;
case STATE_CREDENTIALS:
collectCredentials();
if (mIsPreConfiguredProvider) {
mState = STATE_CHECKING_PRECONFIGURED;
initiateCheckSettingsFragment(SetupDataFragment.CHECK_INCOMING
| SetupDataFragment.CHECK_OUTGOING);
} else {
populateHostAuthsFromSetupData();
if (mSkipAutoDiscover) {
mState = STATE_MANUAL_INCOMING;
updateContentFragment(true /* addToBackstack */);
} else {
mState = STATE_AUTO_DISCOVER;
initiateAutoDiscover();
}
}
break;
case STATE_CHECKING_PRECONFIGURED:
if (mPreConfiguredFailed) {
if (mPasswordFailed) {
// Get rid of the previous instance of the AccountSetupCredentialsFragment.
FragmentManager fm = getFragmentManager();
fm.popBackStackImmediate(CREDENTIALS_BACKSTACK_TAG, 0);
final AccountSetupCredentialsFragment f = (AccountSetupCredentialsFragment)
getContentFragment();
f.setPasswordFailed(mPasswordFailed);
resetStateFromCurrentFragment();
} else {
mState = STATE_MANUAL_INCOMING;
updateContentFragment(true /* addToBackstack */);
}
} else {
mState = STATE_OPTIONS;
updateContentFragment(true /* addToBackstack */);
}
break;
case STATE_AUTO_DISCOVER:
// TODO: figure out if we can skip past manual setup
mState = STATE_MANUAL_INCOMING;
updateContentFragment(true);
break;
case STATE_MANUAL_INCOMING:
onIncomingComplete();
mState = STATE_CHECKING_INCOMING;
initiateCheckSettingsFragment(SetupDataFragment.CHECK_INCOMING);
break;
case STATE_CHECKING_INCOMING:
final EmailServiceUtils.EmailServiceInfo serviceInfo =
mSetupData.getIncomingServiceInfo(this);
if (serviceInfo.usesSmtp) {
mState = STATE_MANUAL_OUTGOING;
} else {
mState = STATE_OPTIONS;
}
updateContentFragment(true /* addToBackstack */);
break;
case STATE_MANUAL_OUTGOING:
onOutgoingComplete();
mState = STATE_CHECKING_OUTGOING;
initiateCheckSettingsFragment(SetupDataFragment.CHECK_OUTGOING);
break;
case STATE_CHECKING_OUTGOING:
mState = STATE_OPTIONS;
updateContentFragment(true /* addToBackstack */);
break;
case STATE_OPTIONS:
mState = STATE_CREATING;
initiateAccountCreation();
break;
case STATE_CREATING:
mState = STATE_NAMES;
updateContentFragment(true /* addToBackstack */);
if (mSetupData.getFlowMode() ==
AuthenticatorSetupIntentHelper.FLOW_MODE_FORCE_CREATE) {
getFragmentManager().executePendingTransactions();
initiateAccountFinalize();
}
break;
case STATE_NAMES:
initiateAccountFinalize();
break;
case STATE_FINALIZE:
finish();
break;
default:
LogUtils.wtf(LogUtils.TAG, "Unknown state %d", mState);
break;
}
}
/**
* Check if we should divert to creating a Gmail account instead
* @return true if we diverted
*/
private boolean possiblyDivertToGmail() {
// TODO: actually divert here
final EmailServiceUtils.EmailServiceInfo info =
mSetupData.getIncomingServiceInfo(this);
if (TextUtils.equals(info.protocol, "gmail")) {
final Bundle options = new Bundle(1);
options.putBoolean("allowSkip", false);
AccountManager.get(this).addAccount("com.google",
"mail" /* authTokenType */,
null,
options,
this, null, null);
finish();
return true;
}
return false;
}
/**
* Block the back key if we are currently processing the "next" key"
*/
@Override
public void onBackPressed() {
if (mIsProcessing) {
return;
}
if (mState == STATE_NAMES) {
finish();
} else {
super.onBackPressed();
}
// After super.onBackPressed() our fragment should be in place, so query the state we
// installed it for
resetStateFromCurrentFragment();
}
@Override
public void setAccount(Account account) {
mSetupData.setAccount(account);
}
@Override
public void finish() {
// If the account manager initiated the creation, and success was not reported,
// then we assume that we're giving up (for any reason) - report failure.
if (mReportAccountAuthenticatorError) {
if (mAccountAuthenticatorResponse != null) {
mAccountAuthenticatorResponse
.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
mAccountAuthenticatorResponse = null;
}
}
super.finish();
}
@Override
public void onNextButton() {
// Some states are handled without UI, block double-presses here
if (!mIsProcessing) {
proceed();
}
}
/**
* @return true to proceed, false to remain on the current screen
*/
private boolean onBasicsComplete() {
final AccountSetupBasicsFragment f = (AccountSetupBasicsFragment) getContentFragment();
final String email = f.getEmail();
// Reset the protocol choice in case the user has back-navigated here
mSetupData.setIncomingProtocol(this, null);
if (!TextUtils.equals(email, mSetupData.getEmail())) {
// If the user changes their email address, clear the password failed state
mPasswordFailed = false;
}
mSetupData.setEmail(email);
final String[] emailParts = email.split("@");
final String domain = emailParts[1].trim();
mProvider = AccountSettingsUtils.findProviderForDomain(this, domain);
if (mProvider != null) {
mIsPreConfiguredProvider = true;
if (mProvider.note != null) {
final AccountSetupNoteDialogFragment dialogFragment =
AccountSetupNoteDialogFragment.newInstance(mProvider.note);
dialogFragment.show(getFragmentManager(), AccountSetupNoteDialogFragment.TAG);
return false;
} else {
return finishAutoSetup();
}
} else {
mIsPreConfiguredProvider = false;
final String existingAccountName =
mExistingAccountsMap != null ? mExistingAccountsMap.get(email) : null;
if (!TextUtils.isEmpty(existingAccountName)) {
showDuplicateAccountDialog(existingAccountName);
return false;
} else {
populateSetupData(mOwnerName, email);
mSkipAutoDiscover = false;
return true;
}
}
}
private void showDuplicateAccountDialog(final String existingAccountName) {
final DuplicateAccountDialogFragment dialogFragment =
DuplicateAccountDialogFragment.newInstance(existingAccountName);
dialogFragment.show(getFragmentManager(), DuplicateAccountDialogFragment.TAG);
}
@Override
public void onDuplicateAccountDialogDismiss() {
resetStateFromCurrentFragment();
}
private boolean shouldDivertToManual() {
final AccountSetupBasicsFragment f = (AccountSetupBasicsFragment) getContentFragment();
return f.isManualSetup();
}
@Override
public void onCredentialsComplete(Bundle results) {
proceed();
}
private void collectCredentials() {
final AccountSetupCredentialsFragment f = (AccountSetupCredentialsFragment)
getContentFragment();
final Bundle results = f.getCredentialResults();
mSetupData.setCredentialResults(results);
final Account account = mSetupData.getAccount();
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
AccountSetupCredentialsFragment.populateHostAuthWithResults(this, recvAuth,
mSetupData.getCredentialResults());
mSetupData.setIncomingCredLoaded(true);
final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
if (info.usesSmtp) {
final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
AccountSetupCredentialsFragment.populateHostAuthWithResults(this, sendAuth,
mSetupData.getCredentialResults());
mSetupData.setOutgoingCredLoaded(true);
}
}
@Override
public void onNoteDialogComplete() {
finishAutoSetup();
proceed();
}
@Override
public void onNoteDialogCancel() {
resetStateFromCurrentFragment();
}
/**
* Finish the auto setup process, in some cases after showing a warning dialog.
* Happens after onBasicsComplete
* @return true to proceed, false to remain on the current screen
*/
private boolean finishAutoSetup() {
final String email = mSetupData.getEmail();
try {
mProvider.expandTemplates(email);
final String primaryProtocol = HostAuth.getProtocolFromString(mProvider.incomingUri);
EmailServiceUtils.EmailServiceInfo info =
EmailServiceUtils.getServiceInfo(this, primaryProtocol);
// If the protocol isn't one we can use, and we're not diverting to gmail, try the alt
if (!info.isGmailStub && !EmailServiceUtils.isServiceAvailable(this, info.protocol)) {
LogUtils.d(LogUtils.TAG, "Protocol %s not available, using alternate",
info.protocol);
mProvider.expandAlternateTemplates(email);
final String alternateProtocol = HostAuth.getProtocolFromString(
mProvider.incomingUri);
info = EmailServiceUtils.getServiceInfo(this, alternateProtocol);
}
final Account account = mSetupData.getAccount();
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
recvAuth.setHostAuthFromString(mProvider.incomingUri);
recvAuth.setUserName(mProvider.incomingUsername);
recvAuth.mPort =
((recvAuth.mFlags & HostAuth.FLAG_SSL) != 0) ? info.portSsl : info.port;
if (info.usesSmtp) {
final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
sendAuth.setHostAuthFromString(mProvider.outgoingUri);
sendAuth.setUserName(mProvider.outgoingUsername);
}
// Populate the setup data, assuming that the duplicate account check will succeed
populateSetupData(mOwnerName, email);
final String duplicateAccountName =
mExistingAccountsMap != null ? mExistingAccountsMap.get(email) : null;
if (duplicateAccountName != null) {
showDuplicateAccountDialog(duplicateAccountName);
return false;
}
} catch (URISyntaxException e) {
mSkipAutoDiscover = false;
mPreConfiguredFailed = true;
}
return true;
}
/**
* Helper method to fill in some per-protocol defaults
* @param account Account object to fill in
*/
public void setDefaultsForProtocol(Account account) {
final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
if (info == null) return;
account.setSyncInterval(info.defaultSyncInterval);
account.mSyncLookback = info.defaultLookback;
if (info.offerLocalDeletes) {
account.setDeletePolicy(info.defaultLocalDeletes);
}
}
/**
* Populate SetupData's account with complete setup info, assumes that the receive auth is
* created and its protocol is set
*/
private void populateSetupData(String senderName, String senderEmail) {
final Account account = mSetupData.getAccount();
account.setSenderName(senderName);
account.setEmailAddress(senderEmail);
account.setDisplayName(senderEmail);
setDefaultsForProtocol(account);
}
private void onIncomingComplete() {
AccountSetupIncomingFragment f = (AccountSetupIncomingFragment) getContentFragment();
f.collectUserInput();
}
private void onOutgoingComplete() {
AccountSetupOutgoingFragment f = (AccountSetupOutgoingFragment) getContentFragment();
f.collectUserInput();
}
// This callback method is only applicable to using Incoming/Outgoing fragments in settings mode
@Override
public void onAccountServerUIComplete(int checkMode) {}
// This callback method is only applicable to using Incoming/Outgoing fragments in settings mode
@Override
public void onAccountServerSaveComplete() {}
private void populateHostAuthsFromSetupData() {
final String email = mSetupData.getEmail();
final String[] emailParts = email.split("@");
final String domain = emailParts[1];
final Account account = mSetupData.getAccount();
final EmailServiceUtils.EmailServiceInfo info =
mSetupData.getIncomingServiceInfo(this);
final HostAuth recvAuth = account.getOrCreateHostAuthRecv(this);
recvAuth.setUserName(email);
recvAuth.setConnection(mSetupData.getIncomingProtocol(), domain,
HostAuth.PORT_UNKNOWN, info.offerTls ? HostAuth.FLAG_TLS : HostAuth.FLAG_SSL);
AccountSetupCredentialsFragment.populateHostAuthWithResults(this, recvAuth,
mSetupData.getCredentialResults());
mSetupData.setIncomingCredLoaded(true);
if (info.usesSmtp) {
final HostAuth sendAuth = account.getOrCreateHostAuthSend(this);
sendAuth.setUserName(email);
sendAuth.setConnection(HostAuth.LEGACY_SCHEME_SMTP, domain,
HostAuth.PORT_UNKNOWN, HostAuth.FLAG_TLS);
AccountSetupCredentialsFragment.populateHostAuthWithResults(this, sendAuth,
mSetupData.getCredentialResults());
mSetupData.setOutgoingCredLoaded(true);
}
}
private void initiateAutoDiscover() {
// Populate the setup data, assuming that the duplicate account check will succeed
initiateCheckSettingsFragment(SetupDataFragment.CHECK_AUTODISCOVER);
}
private void initiateCheckSettingsFragment(int checkMode) {
final Fragment f = AccountCheckSettingsFragment.newInstance(checkMode);
final Fragment d = CheckSettingsProgressDialogFragment.newInstance(checkMode);
getFragmentManager().beginTransaction()
.add(f, AccountCheckSettingsFragment.TAG)
.add(d, CheckSettingsProgressDialogFragment.TAG)
.commit();
}
@Override
public void onCheckSettingsProgressDialogCancel() {
dismissCheckSettingsFragment();
resetStateFromCurrentFragment();
}
private void dismissCheckSettingsFragment() {
final Fragment f = getFragmentManager().findFragmentByTag(AccountCheckSettingsFragment.TAG);
final Fragment d =
getFragmentManager().findFragmentByTag(CheckSettingsProgressDialogFragment.TAG);
getFragmentManager().beginTransaction()
.remove(f)
.remove(d)
.commit();
}
@Override
public void onCheckSettingsError(int reason, String message) {
if (reason == CheckSettingsErrorDialogFragment.REASON_AUTHENTICATION_FAILED ||
reason == CheckSettingsErrorDialogFragment.REASON_CERTIFICATE_REQUIRED) {
// TODO: possibly split password and cert error conditions
mPasswordFailed = true;
}
dismissCheckSettingsFragment();
final DialogFragment f =
CheckSettingsErrorDialogFragment.newInstance(reason, message);
f.show(getFragmentManager(), CheckSettingsErrorDialogFragment.TAG);
}
@Override
public void onCheckSettingsErrorDialogEditCertificate() {
if (mState == STATE_CHECKING_PRECONFIGURED) {
mPreConfiguredFailed = true;
proceed();
} else {
resetStateFromCurrentFragment();
}
final AccountSetupIncomingFragment f = (AccountSetupIncomingFragment) getContentFragment();
f.onCertificateRequested();
}
@Override
public void onCheckSettingsErrorDialogEditSettings() {
// If we're checking pre-configured, set a flag that we failed and navigate forwards to
// incoming settings
if (mState == STATE_CHECKING_PRECONFIGURED || mState == STATE_AUTO_DISCOVER) {
mPreConfiguredFailed = true;
proceed();
} else {
resetStateFromCurrentFragment();
}
}
@Override
public void onCheckSettingsComplete() {
mPreConfiguredFailed = false;
mPasswordFailed = false;
dismissCheckSettingsFragment();
proceed();
}
@Override
public void onCheckSettingsAutoDiscoverComplete(int result) {
dismissCheckSettingsFragment();
proceed();
}
@Override
public void onCheckSettingsSecurityRequired(String hostName) {
dismissCheckSettingsFragment();
final DialogFragment f = SecurityRequiredDialogFragment.newInstance(hostName);
f.show(getFragmentManager(), SecurityRequiredDialogFragment.TAG);
}
@Override
public void onSecurityRequiredDialogResult(boolean ok) {
if (ok) {
proceed();
} else {
resetStateFromCurrentFragment();
}
}
@Override
public void onChooseProtocol(String protocol) {
mSetupData.setIncomingProtocol(this, protocol);
final Account account = mSetupData.getAccount();
setDefaultsForProtocol(account);
proceed();
}
@Override
public void onABProtocolDisambiguated(String chosenProtocol) {
if (!TextUtils.equals(mSetupData.getIncomingProtocol(this), chosenProtocol)) {
mIsPreConfiguredProvider = false;
mSetupData.setIncomingProtocol(this, chosenProtocol);
final Account account = mSetupData.getAccount();
setDefaultsForProtocol(account);
}
proceed();
}
/**
* Ths is called when the user clicks the "done" button.
* It collects the data from the UI, updates the setup account record, and launches a fragment
* which handles creating the account in the system and database.
*/
private void initiateAccountCreation() {
mIsProcessing = true;
getContentFragment().setNextButtonEnabled(false);
final Account account = mSetupData.getAccount();
if (account.mHostAuthRecv == null) {
throw new IllegalStateException("in AccountSetupOptions with null mHostAuthRecv");
}
final AccountSetupOptionsFragment fragment = (AccountSetupOptionsFragment)
getContentFragment();
if (fragment == null) {
throw new IllegalStateException("Fragment missing!");
}
account.setDisplayName(account.getEmailAddress());
int newFlags = account.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS);
final EmailServiceUtils.EmailServiceInfo serviceInfo =
mSetupData.getIncomingServiceInfo(this);
if (serviceInfo.offerAttachmentPreload && fragment.getBackgroundAttachmentsValue()) {
newFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS;
}
final HostAuth hostAuth = account.getOrCreateHostAuthRecv(this);
if (hostAuth.mProtocol.equals(getString(R.string.protocol_eas))) {
try {
final double protocolVersionDouble = Double.parseDouble(account.mProtocolVersion);
if (protocolVersionDouble >= 12.0) {
// If the the account is EAS and the protocol version is above 12.0,
// we know that SmartForward is enabled and the various search flags
// should be enabled first.
// TODO: Move this into protocol specific code in the future.
newFlags |= Account.FLAGS_SUPPORTS_SMART_FORWARD |
Account.FLAGS_SUPPORTS_GLOBAL_SEARCH | Account.FLAGS_SUPPORTS_SEARCH;
}
} catch (NumberFormatException e) {
LogUtils.wtf(LogUtils.TAG, e, "Exception thrown parsing the protocol version.");
}
}
account.setFlags(newFlags);
account.setSyncInterval(fragment.getCheckFrequencyValue());
final Integer syncWindowValue = fragment.getAccountSyncWindowValue();
if (syncWindowValue != null) {
account.setSyncLookback(syncWindowValue);
}
// Finish setting up the account, and commit it to the database
if (mSetupData.getPolicy() != null) {
account.mFlags |= Account.FLAGS_SECURITY_HOLD;
account.mPolicy = mSetupData.getPolicy();
}
// Finally, write the completed account (for the first time) and then
// install it into the Account manager as well. These are done off-thread.
// The account manager will report back via the callback, which will take us to
// the next operations.
final boolean syncEmail = fragment.getSyncEmailValue();
final boolean syncCalendar = serviceInfo.syncCalendar && fragment.getSyncCalendarValue();
final boolean syncContacts = serviceInfo.syncContacts && fragment.getSyncContactsValue();
final boolean enableNotifications = fragment.getNotifyValue();
final Fragment f = AccountCreationFragment.newInstance(account, syncEmail, syncCalendar,
syncContacts, enableNotifications);
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(f, AccountCreationFragment.TAG);
ft.commit();
showCreateAccountDialog();
}
/**
* Called by the account creation fragment after it has completed.
* We do a small amount of work here before moving on to the next state.
*/
@Override
public void onAccountCreationFragmentComplete() {
destroyAccountCreationFragment();
// If the account manager initiated the creation, and success was not reported,
// then we assume that we're giving up (for any reason) - report failure.
if (mAccountAuthenticatorResponse != null) {
final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
final Bundle b = new Bundle(2);
b.putString(AccountManager.KEY_ACCOUNT_NAME, mSetupData.getEmail());
b.putString(AccountManager.KEY_ACCOUNT_TYPE, info.accountType);
mAccountAuthenticatorResponse.onResult(b);
mAccountAuthenticatorResponse = null;
mReportAccountAuthenticatorError = false;
}
setResult(RESULT_OK);
proceed();
}
@Override
public void destroyAccountCreationFragment() {
dismissCreateAccountDialog();
final Fragment f = getFragmentManager().findFragmentByTag(AccountCreationFragment.TAG);
if (f == null) {
LogUtils.wtf(LogUtils.TAG, "Couldn't find AccountCreationFragment to destroy");
}
getFragmentManager().beginTransaction()
.remove(f)
.commit();
}
public static class CreateAccountDialogFragment extends DialogFragment {
public static final String TAG = "CreateAccountDialogFragment";
public CreateAccountDialogFragment() {}
public static CreateAccountDialogFragment newInstance() {
return new CreateAccountDialogFragment();
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
/// Show "Creating account..." dialog
setCancelable(false);
final ProgressDialog d = new ProgressDialog(getActivity());
d.setIndeterminate(true);
d.setMessage(getString(R.string.account_setup_creating_account_msg));
return d;
}
}
protected void showCreateAccountDialog() {
CreateAccountDialogFragment.newInstance()
.show(getFragmentManager(), CreateAccountDialogFragment.TAG);
}
protected void dismissCreateAccountDialog() {
final DialogFragment f = (DialogFragment)
getFragmentManager().findFragmentByTag(CreateAccountDialogFragment.TAG);
if (f != null) {
f.dismiss();
}
}
public static class CreateAccountErrorDialogFragment extends DialogFragment
implements DialogInterface.OnClickListener {
public CreateAccountErrorDialogFragment() {}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String message = getString(R.string.account_setup_failed_dlg_auth_message,
R.string.system_account_create_failed);
setCancelable(false);
return new AlertDialog.Builder(getActivity())
.setIconAttribute(android.R.attr.alertDialogIcon)
.setTitle(R.string.account_setup_failed_dlg_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, this)
.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
getActivity().finish();
}
}
/**
* This is called if MailService.setupAccountManagerAccount() fails for some reason
*/
@Override
public void showCreateAccountErrorDialog() {
new CreateAccountErrorDialogFragment().show(getFragmentManager(), null);
}
/**
* Collect the data from AccountSetupNames and finish up account creation
*/
private void initiateAccountFinalize() {
mIsProcessing = true;
getContentFragment().setNextButtonEnabled(false);
AccountSetupNamesFragment fragment = (AccountSetupNamesFragment) getContentFragment();
// Update account object from UI
final Account account = mSetupData.getAccount();
final String description = fragment.getDescription();
if (!TextUtils.isEmpty(description)) {
account.setDisplayName(description);
}
account.setSenderName(fragment.getSenderName());
final Fragment f = AccountFinalizeFragment.newInstance(account);
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(f, AccountFinalizeFragment.TAG);
ft.commit();
}
/**
* Called when the AccountFinalizeFragment has finished its tasks
*/
@Override
public void onAccountFinalizeFragmentComplete() {
finish();
}
}