Prevent flicker in opening Email.

Welcome had some assumptions that some things had to be done
asynchronously, such as checking inbox status.
This can now be done on the UI thread and so transition into Email can
be done immediately in onCreate, except if there's reconciliating to
be done

Bug: 4599569
Change-Id: Iaaac21e73c985c60e1b7974fb0429948b35968e4
This commit is contained in:
Ben Komalo 2011-09-15 15:18:26 -07:00
parent 3415be2078
commit 002a1802ca
4 changed files with 160 additions and 86 deletions

View File

@ -91,10 +91,6 @@ public class Welcome extends Activity {
private View mWaitingForSyncView;
// Account reconciler is started from AccountResolver, which we may run multiple times,
// so remember if we did it already to prevent from running it twice.
private boolean mAccountsReconciled;
private long mAccountId;
private long mMailboxId;
private long mMessageId;
@ -195,7 +191,19 @@ public class Welcome extends Activity {
mAccountUuid = IntentUtilities.getAccountUuidFromIntent(intent);
UiUtilities.setDebugPaneMode(getDebugPaneMode(intent));
startAccountResolver();
// Reconcile POP/IMAP accounts. EAS accounts are taken care of by ExchangeService.
if (MailService.hasMismatchInPopImapAccounts(this)) {
EmailAsyncTask.runAsyncParallel(new Runnable() {
@Override
public void run() {
// Reconciling can be heavy - so do it in the background.
MailService.reconcilePopImapAccountsSync(Welcome.this);
resolveAccount();
}
});
} else {
resolveAccount();
}
// Reset the "accounts changed" notification, now that we're here
Email.setNotifyUiAccountsChanged(false);
@ -254,10 +262,6 @@ public class Welcome extends Activity {
super.startActivity(intent);
}
private void startAccountResolver() {
new AccountResolver().executeParallel();
}
/**
* Stop inbox lookup. This MSUT be called on the UI thread.
*/
@ -334,56 +338,22 @@ public class Welcome extends Activity {
* 2b. Otherwise open the main activity.
* </pre>
*/
private class AccountResolver extends EmailAsyncTask<Void, Void, Void> {
private boolean mStartAccountSetup;
private boolean mStartInboxLookup;
public AccountResolver() {
super(mTaskTracker);
}
@Override
protected Void doInBackground(Void... params) {
final Activity activity = Welcome.this;
if (!mAccountsReconciled) {
mAccountsReconciled = true;
// Reconcile POP/IMAP accounts. EAS accounts are taken care of by ExchangeService.
//
// TODO Do we still really have to do it at startup?
// Now that we use the broadcast to detect system account changes, our database
// should always be in sync with the system accounts...
MailService.reconcilePopImapAccountsSync(activity);
}
final int numAccount = EmailContent.count(activity, Account.CONTENT_URI);
if (numAccount == 0) {
mStartAccountSetup = true;
} else {
mAccountId = resolveAccountId(activity, mAccountId, mAccountUuid);
if (Account.isNormalAccount(mAccountId) &&
Mailbox.findMailboxOfType(activity, mAccountId, Mailbox.TYPE_INBOX)
== Mailbox.NO_MAILBOX) {
mStartInboxLookup = true;
}
}
return null;
}
@Override
protected void onSuccess(Void noResult) {
final Activity activity = Welcome.this;
if (mStartAccountSetup) {
AccountSetupBasics.actionNewAccount(activity);
activity.finish();
} else if (mStartInboxLookup) {
private void resolveAccount() {
final int numAccount = EmailContent.count(this, Account.CONTENT_URI);
boolean startAccountSetup = false;
if (numAccount == 0) {
AccountSetupBasics.actionNewAccount(this);
finish();
} else {
mAccountId = resolveAccountId(this, mAccountId, mAccountUuid);
if (Account.isNormalAccount(mAccountId) &&
Mailbox.findMailboxOfType(this, mAccountId, Mailbox.TYPE_INBOX)
== Mailbox.NO_MAILBOX) {
startInboxLookup();
} else {
startEmailActivity();
return;
}
}
startEmailActivity();
}
/**
@ -420,8 +390,8 @@ public class Welcome extends Activity {
mMessageId = Message.NO_MESSAGE;
mAccountUuid = null;
// Restart the task.
startAccountResolver();
// Restart the account resolution.
resolveAccount();
}
@Override

View File

@ -36,7 +36,25 @@ public class AccountReconciler {
// of reconcilation. This is for unit test purposes only; the caller may NOT be in the same
// package as this class, so we make the constant public.
@VisibleForTesting
public static final String ACCOUNT_MANAGER_ACCOUNT_TEST_PREFIX = " _";
static final String ACCOUNT_MANAGER_ACCOUNT_TEST_PREFIX = " _";
/**
* Checks two account lists to see if there is any reconciling to be done. Can be done on the
* UI thread.
* @param context the app context
* @param emailProviderAccounts accounts as reported in the Email provider
* @param accountManagerAccounts accounts as reported by the system account manager, for the
* particular protocol types that match emailProviderAccounts
*/
public static boolean accountsNeedReconciling(
final Context context,
List<Account> emailProviderAccounts,
android.accounts.Account[] accountManagerAccounts) {
return reconcileAccountsInternal(
context, emailProviderAccounts, accountManagerAccounts,
context, false /* performReconciliation */);
}
/**
* Compare our account list (obtained from EmailProvider) with the account list owned by
@ -54,12 +72,31 @@ public class AccountReconciler {
* @param accountManagerAccounts The account manager accounts to work from
* @param providerContext application provider context
*/
public static boolean reconcileAccounts(Context context,
List<Account> emailProviderAccounts, android.accounts.Account[] accountManagerAccounts,
public static void reconcileAccounts(
Context context,
List<Account> emailProviderAccounts,
android.accounts.Account[] accountManagerAccounts,
Context providerContext) {
reconcileAccountsInternal(
context, emailProviderAccounts, accountManagerAccounts,
providerContext, true /* performReconciliation */);
}
/**
* Internal method to actually perform reconciliation, or simply check that it needs to be done
* and avoid doing any heavy work, depending on the value of the passed in
* {@code performReconciliation}.
*/
private static boolean reconcileAccountsInternal(
Context context,
List<Account> emailProviderAccounts,
android.accounts.Account[] accountManagerAccounts,
Context providerContext,
boolean performReconciliation) {
boolean needsReconciling = false;
// First, look through our EmailProvider accounts to make sure there's a corresponding
// AccountManager account
boolean accountsDeleted = false;
for (Account providerAccount: emailProviderAccounts) {
String providerAccountName = providerAccount.mEmailAddress;
boolean found = false;
@ -75,13 +112,16 @@ public class AccountReconciler {
"Account reconciler noticed incomplete account; ignoring");
continue;
}
// This account has been deleted in the AccountManager!
Log.d(Logging.LOG_TAG,
"Account deleted in AccountManager; deleting from provider: " +
providerAccountName);
Controller.getInstance(context).deleteAccountSync(providerAccount.mId,
providerContext);
accountsDeleted = true;
needsReconciling = true;
if (performReconciliation) {
// This account has been deleted in the AccountManager!
Log.d(Logging.LOG_TAG,
"Account deleted in AccountManager; deleting from provider: " +
providerAccountName);
Controller.getInstance(context).deleteAccountSync(providerAccount.mId,
providerContext);
}
}
}
// Now, look through AccountManager accounts to make sure we have a corresponding cached EAS
@ -99,26 +139,30 @@ public class AccountReconciler {
}
if (!found) {
// This account has been deleted from the EmailProvider database
Log.d(Logging.LOG_TAG,
"Account deleted from provider; deleting from AccountManager: " +
accountManagerAccountName);
// Delete the account
AccountManagerFuture<Boolean> blockingResult = AccountManager.get(context)
needsReconciling = true;
if (performReconciliation) {
Log.d(Logging.LOG_TAG,
"Account deleted from provider; deleting from AccountManager: " +
accountManagerAccountName);
// Delete the account
AccountManagerFuture<Boolean> blockingResult = AccountManager.get(context)
.removeAccount(accountManagerAccount, null, null);
try {
// Note: All of the potential errors from removeAccount() are simply logged
// here, as there is nothing to actually do about them.
blockingResult.getResult();
} catch (OperationCanceledException e) {
Log.w(Logging.LOG_TAG, e.toString());
} catch (AuthenticatorException e) {
Log.w(Logging.LOG_TAG, e.toString());
} catch (IOException e) {
Log.w(Logging.LOG_TAG, e.toString());
try {
// Note: All of the potential errors from removeAccount() are simply logged
// here, as there is nothing to actually do about them.
blockingResult.getResult();
} catch (OperationCanceledException e) {
Log.w(Logging.LOG_TAG, e.toString());
} catch (AuthenticatorException e) {
Log.w(Logging.LOG_TAG, e.toString());
} catch (IOException e) {
Log.w(Logging.LOG_TAG, e.toString());
}
}
accountsDeleted = true;
}
}
return accountsDeleted;
return needsReconciling;
}
}

View File

@ -713,6 +713,18 @@ public class MailService extends Service {
sReconcilePopImapAccountsSyncExecutor.run(context);
}
/**
* Determines whether or not POP/IMAP accounts need reconciling or not. This is a safe operation
* to perform on the UI thread.
*/
public static boolean hasMismatchInPopImapAccounts(Context context) {
android.accounts.Account[] accountManagerAccounts = AccountManager.get(context)
.getAccountsByType(AccountManagerTypes.TYPE_POP_IMAP);
ArrayList<Account> providerAccounts = getPopImapAccountList(context);
return AccountReconciler.accountsNeedReconciling(
context, providerAccounts, accountManagerAccounts);
}
/**
* See Utility.reconcileAccounts for details
* @param context The context in which to operate

View File

@ -26,6 +26,7 @@ import android.content.pm.PackageManager;
import com.android.email.AccountTestCase;
import com.android.email.Controller;
import com.android.email.provider.AccountReconciler;
import com.android.email.provider.EmailProvider;
import com.android.email.provider.ProviderTestUtils;
import com.android.email.service.MailService.AccountSyncReport;
@ -33,7 +34,9 @@ import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent;
import com.android.emailcommon.provider.HostAuth;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Tests of the Email provider.
@ -163,6 +166,51 @@ public class MailServiceTests extends AccountTestCase {
// ... and it should be account "3"
assertEquals(getTestAccountEmailAddress("3"), accountManagerAccounts[0].name);
}
public void testReconcileDetection() {
Context context = getContext();
List<Account> providerAccounts;
android.accounts.Account[] accountManagerAccounts;
android.accounts.Account[] baselineAccounts =
AccountManager.get(context).getAccountsByType(TEST_ACCOUNT_TYPE);
// Empty lists match.
providerAccounts = new ArrayList<Account>();
accountManagerAccounts = new android.accounts.Account[0];
assertFalse(AccountReconciler.accountsNeedReconciling(
context, providerAccounts, accountManagerAccounts));
setupProviderAndAccountManagerAccount(getTestAccountName("1"));
accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
providerAccounts = makeExchangeServiceAccountList();
// A single account, but empty list on the other side is detected as needing reconciliation
assertTrue(AccountReconciler.accountsNeedReconciling(
context, new ArrayList<Account>(), accountManagerAccounts));
assertTrue(AccountReconciler.accountsNeedReconciling(
context, providerAccounts, new android.accounts.Account[0]));
// Note that no reconciliation should have happened though - we just wanted to detect it.
assertEquals(1, makeExchangeServiceAccountList().size());
assertEquals(1, getAccountManagerAccounts(baselineAccounts).length);
// Single account matches - no reconciliation should be detected.
assertFalse(AccountReconciler.accountsNeedReconciling(
context, providerAccounts, accountManagerAccounts));
// Provider: 1,2,3. AccountManager: 1, 3.
String username = getTestAccountName("2");
ProviderTestUtils.setupAccount(getTestAccountName("2"), true, getMockContext());
setupProviderAndAccountManagerAccount(getTestAccountName("3"));
accountManagerAccounts = getAccountManagerAccounts(baselineAccounts);
providerAccounts = makeExchangeServiceAccountList();
assertTrue(AccountReconciler.accountsNeedReconciling(
context, providerAccounts, accountManagerAccounts));
}
/**
* Lightweight subclass of the Controller class allows injection of mock context
*/