Legacy account migration

* Create new activity to encapsulate account upgrade
* Populate it with a list of legacy accounts, and progress bars for each
* Sidestep Welcome when there are legacy accounts to convert
* Super lightweight account migration:
  - Account login info only
  - no folders, messages, or attachments
* Scrub out old data
* Return to Welcome screen

As noted, the copies working (useable) POP & IMAP accounts, but does
not try to deal with folders, messages, or attachments.

Bug: 2065528
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8eaa175..473ca67 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -55,14 +55,20 @@
 
     <application android:icon="@drawable/icon" android:label="@string/app_name"
         android:name="Email">
-        <activity android:name=".activity.Welcome">
+        <activity
+            android:name=".activity.Welcome">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-
+        <activity
+            android:name=".activity.UpgradeAccounts"
+            android:label="@string/upgrade_accounts_title"
+            android:theme="@android:style/Theme.NoTitleBar"
+            android:configChanges="keyboardHidden|orientation" >
+        </activity>
         <!-- Must be exported in order for the AccountManager to launch it -->
         <activity
             android:name=".activity.setup.AccountSetupBasics"
diff --git a/res/layout/upgrade_accounts.xml b/res/layout/upgrade_accounts.xml
new file mode 100644
index 0000000..cbe3169
--- /dev/null
+++ b/res/layout/upgrade_accounts.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:background="@*android:drawable/title_bar_medium">
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:text="@string/upgrade_accounts_title"
+            android:gravity="center"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textColor="?android:attr/textColorPrimary"
+            android:shadowColor="?android:attr/colorBackground"
+            android:shadowRadius="2" />
+    </LinearLayout>
+    <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="0px"
+            android:layout_weight="1"
+            android:paddingTop="10dip"
+            android:paddingBottom="10dip">
+        <ListView android:id="@android:id/list"
+                android:layout_width="match_parent" 
+                android:layout_height="match_parent"
+                android:drawSelectorOnTop="false"
+                android:fastScrollEnabled="true" />
+    </FrameLayout>
+    <LinearLayout style="@android:style/ButtonBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <View
+           android:layout_width="0dip"
+           android:layout_height="0dip"
+           android:layout_weight="1" />
+        <Button android:id="@+id/action_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1" 
+            android:text="@string/okay_action" />
+        <View
+           android:layout_width="0dip"
+           android:layout_height="0dip"
+           android:layout_weight="1" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/upgrade_accounts_item.xml b/res/layout/upgrade_accounts_item.xml
new file mode 100644
index 0000000..c4dfe9f
--- /dev/null
+++ b/res/layout/upgrade_accounts_item.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2010, 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="vertical"
+    android:paddingRight="6dip"
+    android:paddingLeft="6dip"
+    android:gravity="fill" >
+
+    <TextView android:id="@+id/name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:textStyle="bold"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:layout_marginBottom="2dip" />
+
+    <ProgressBar android:id="@+id/progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:max="100" />
+
+    <TextView android:id="@+id/error"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:singleLine="true"
+        android:ellipsize="marquee"
+        android:layout_marginBottom="2dip" />
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 807bb6f..a4bb07a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -573,6 +573,10 @@
     <!-- Message of Remove account confirmation dialog box -->
     <string name="account_delete_dlg_instructions_fmt">The account \"<xliff:g id="account">%s</xliff:g>\" will be removed from Email.</string>
 
+    <!-- Title of Upgrade Accounts activity -->
+    <string name="upgrade_accounts_title">Upgrade accounts</string>
+    <string name="upgrade_accounts_error">Unable to upgrade account</string>
+
     <!-- Message that appears when user adds a Yahoo mail account. This alert has no title. -->
     <string name="provider_note_yahoo">Mailbox access is not supported for some types of
         Yahoo! mail accounts.  If you have trouble connecting, visit yahoo.com for more
diff --git a/src/com/android/email/Account.java b/src/com/android/email/Account.java
index 3fa5a8b..9301a87 100644
--- a/src/com/android/email/Account.java
+++ b/src/com/android/email/Account.java
@@ -450,6 +450,14 @@
         mSyncWindow = window;
     }
 
+    public int getBackupFlags() {
+        return mBackupFlags;
+    }
+
+    public void setBackupFlags(int flags) {
+        mBackupFlags = flags;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o instanceof Account) {
diff --git a/src/com/android/email/LegacyConversions.java b/src/com/android/email/LegacyConversions.java
index 4eae723..1b96d98 100644
--- a/src/com/android/email/LegacyConversions.java
+++ b/src/com/android/email/LegacyConversions.java
@@ -579,7 +579,7 @@
      * @param fromAccount the legacy account to convert to modern format
      * @return an Account ready to be committed to provider
      */
-    /* package */ static EmailContent.Account makeAccount(Context context, Account fromAccount) {
+    public static EmailContent.Account makeAccount(Context context, Account fromAccount) {
 
         EmailContent.Account result = new EmailContent.Account();
 
diff --git a/src/com/android/email/activity/UpgradeAccounts.java b/src/com/android/email/activity/UpgradeAccounts.java
new file mode 100644
index 0000000..ff3f41e
--- /dev/null
+++ b/src/com/android/email/activity/UpgradeAccounts.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2008 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;
+
+import com.android.email.Account;
+import com.android.email.Email;
+import com.android.email.LegacyConversions;
+import com.android.email.Preferences;
+import com.android.email.R;
+import com.android.email.mail.Folder;
+import com.android.email.mail.MessagingException;
+import com.android.email.mail.Store;
+import com.android.email.mail.store.LocalStore;
+import com.android.email.provider.EmailContent;
+import com.android.email.provider.EmailContent.AccountColumns;
+
+import android.app.Activity;
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.View.OnClickListener;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+/**
+ * This activity will be used whenever we have a large/slow bulk upgrade operation.
+ *
+ * Note: It's preferable to check for "accounts needing upgrade" before launching this
+ * activity, so as to not waste time before every launch.
+ *
+ * TODO: Disable orientation changes, to keep the activity from restarting on rotation.  This is
+ *       set in the manifest but for some reason it's not working.
+ * TODO: More work on actual conversions
+ */
+public class UpgradeAccounts extends ListActivity implements OnClickListener {
+
+    private AccountInfo[] mLegacyAccounts;
+    private UIHandler mHandler = new UIHandler();
+    private AccountsAdapter mAdapter;
+    private ListView mListView;
+    private Button mProceedButton;
+    private ConversionTask mConversionTask;
+    
+    /** This projection is for looking up accounts by their legacy UUID */
+    private static final String WHERE_ACCOUNT_UUID_IS = AccountColumns.COMPATIBILITY_UUID + "=?";
+
+    public static void actionStart(Activity fromActivity) {
+        Intent i = new Intent(fromActivity, UpgradeAccounts.class);
+        fromActivity.startActivity(i);
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        Preferences p = Preferences.getPreferences(this);
+        loadAccountInfoArray(p.getAccounts());
+
+        Log.d(Email.LOG_TAG, "*** Preparing to upgrade " +
+                Integer.toString(mLegacyAccounts.length) + " accounts");
+
+        setContentView(R.layout.upgrade_accounts);
+        mListView = getListView();
+        mProceedButton = (Button) findViewById(R.id.action_button);
+        mProceedButton.setEnabled(false);
+        mProceedButton.setOnClickListener(this);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        updateList();
+        
+        // Start the big conversion engine
+        mConversionTask = new ConversionTask(mLegacyAccounts);
+        mConversionTask.execute();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (mConversionTask != null &&
+                mConversionTask.getStatus() != ConversionTask.Status.FINISHED) {
+            mConversionTask.cancel(true);
+            mConversionTask = null;
+        }
+    }
+
+    public void onClick(View v) {
+        switch (v.getId()) {
+            case R.id.action_button:
+                onClickOk();
+                break;
+        }
+    }
+
+    private void onClickOk() {
+        Welcome.actionStart(UpgradeAccounts.this);
+        finish();
+    }
+
+    private void updateList() {
+        mAdapter = new AccountsAdapter();
+        getListView().setAdapter(mAdapter);
+    }
+
+    private static class AccountInfo {
+        Account account;
+        int maxProgress;
+        int progress;
+        String error;
+    }
+
+    private void loadAccountInfoArray(Account[] legacyAccounts) {
+        mLegacyAccounts = new AccountInfo[legacyAccounts.length];
+        for (int i = 0; i < legacyAccounts.length; i++) {
+            AccountInfo ai = new AccountInfo();
+            ai.account = legacyAccounts[i];
+            ai.maxProgress = 0;
+            ai.progress = 0;
+            ai.error = null;
+            mLegacyAccounts[i] = ai;
+        }
+    }
+
+    private static class ViewHolder {
+        TextView displayName;
+        ProgressBar progress;
+        TextView errorReport;
+    }
+
+    class AccountsAdapter extends BaseAdapter {
+        final LayoutInflater mInflater;
+        
+        AccountsAdapter() {
+            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        @Override
+        public boolean hasStableIds() {
+            return true;
+        }
+        
+        public int getCount() {
+            return mLegacyAccounts.length;
+        }
+
+        public Object getItem(int position) {
+            return mLegacyAccounts[position];
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            View v;
+            if (convertView == null) {
+                v = newView(parent);
+            } else {
+                v = convertView;
+            }
+            bindView(v, position);
+            return v;
+        }
+        
+        public View newView(ViewGroup parent) {
+            View v = mInflater.inflate(R.layout.upgrade_accounts_item, parent, false);
+            ViewHolder h = new ViewHolder();
+            h.displayName = (TextView) v.findViewById(R.id.name);
+            h.progress = (ProgressBar) v.findViewById(R.id.progress);
+            h.errorReport = (TextView) v.findViewById(R.id.error);
+            v.setTag(h);
+            return v;
+        }
+        
+        public void bindView(View view, int position) {
+            ViewHolder vh = (ViewHolder) view.getTag();
+            AccountInfo ai = mLegacyAccounts[position];
+            vh.displayName.setText(ai.account.getDescription());
+            if (ai.error == null) {
+                vh.errorReport.setVisibility(View.GONE);
+                vh.progress.setVisibility(View.VISIBLE);
+                vh.progress.setMax(ai.maxProgress);
+                vh.progress.setProgress(ai.progress);
+            } else {
+                vh.progress.setVisibility(View.GONE);
+                vh.errorReport.setVisibility(View.VISIBLE);
+                vh.errorReport.setText(ai.error);
+            }
+        }
+    }
+
+    /**
+     * Handler for updating UI from async workers
+     *
+     * TODO: I don't know the right paradigm for updating a progress bar in a ListView.  I'd
+     * like to be able to say, "update it if it's visible, skip it if it's not visible."
+     */
+    class UIHandler extends Handler {
+        private static final int MSG_SET_MAX = 1;
+        private static final int MSG_SET_PROGRESS = 2;
+        private static final int MSG_INC_PROGRESS = 3;
+        private static final int MSG_ERROR = 4;
+
+        @Override
+        public void handleMessage(android.os.Message msg) {
+            switch (msg.what) {
+                case MSG_SET_MAX:
+                    mLegacyAccounts[msg.arg1].maxProgress = msg.arg2;
+                    mListView.invalidateViews();        // find a less annoying way to do that
+                    break;
+                case MSG_SET_PROGRESS:
+                    mLegacyAccounts[msg.arg1].progress = msg.arg2;
+                    mListView.invalidateViews();        // find a less annoying way to do that
+                    break;
+                case MSG_INC_PROGRESS:
+                    mLegacyAccounts[msg.arg1].progress++;
+                    mListView.invalidateViews();        // find a less annoying way to do that
+                    break;
+                case MSG_ERROR:
+                    mLegacyAccounts[msg.arg1].error = (String) msg.obj;
+                    mListView.invalidateViews();        // find a less annoying way to do that
+                    mProceedButton.setEnabled(true);
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+
+        public void setMaxProgress(int accountNum, int max) {
+            android.os.Message msg = android.os.Message.obtain();
+            msg.what = MSG_SET_MAX;
+            msg.arg1 = accountNum;
+            msg.arg2 = max;
+            sendMessage(msg);
+        }
+            
+        public void setProgress(int accountNum, int progress) {
+            android.os.Message msg = android.os.Message.obtain();
+            msg.what = MSG_SET_PROGRESS;
+            msg.arg1 = accountNum;
+            msg.arg2 = progress;
+            sendMessage(msg);
+        }
+
+        public void incProgress(int accountNum) {
+            android.os.Message msg = android.os.Message.obtain();
+            msg.what = MSG_INC_PROGRESS;
+            msg.arg1 = accountNum;
+            sendMessage(msg);
+        }
+
+        // Note: also enables the "OK" button, so we pause when complete
+        public void error(String error) {
+            android.os.Message msg = android.os.Message.obtain();
+            msg.what = MSG_ERROR;
+            msg.obj = error;
+            sendMessage(msg);
+        }
+    }
+
+    /**
+     * Everything above was UI plumbing.  This is the meat of this class - a conversion
+     * engine to rebuild accounts from the "LocalStore" (pre Android 2.0) format to the
+     * "Provider" (2.0 and beyond) format.
+     */
+    private class ConversionTask extends AsyncTask<Void, Void, Void> {
+        UpgradeAccounts.AccountInfo[] mAccountInfo;
+        final Context mContext;
+        final Preferences mPreferences;
+        
+        public ConversionTask(UpgradeAccounts.AccountInfo[] accountInfo) {
+            // TODO: should I copy this?
+            mAccountInfo = accountInfo;
+            mContext = UpgradeAccounts.this;
+            mPreferences = Preferences.getPreferences(mContext);
+        }
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            UIHandler handler = UpgradeAccounts.this.mHandler;
+            // Step 1:  Analyze accounts and generate progress max values
+            for (int i = 0; i < mAccountInfo.length; i++) {
+                int estimate = UpgradeAccounts.estimateWork(mContext, mAccountInfo[i].account);
+                UpgradeAccounts.this.mHandler.setMaxProgress(i, estimate);
+            }
+            
+            // Step 2:  Clean out IMAP accounts
+            for (int i = 0; i < mAccountInfo.length; i++) {
+                if (mAccountInfo[i].error == null) {
+                    cleanImapAccount(mContext, mAccountInfo[i].account, i, handler);
+                }
+            }
+
+            // Step 3:  Copy accounts (and delete old accounts)
+            for (int i = 0; i < mAccountInfo.length; i++) {
+                if (mAccountInfo[i].error == null) {
+                    copyAccount(mContext, mAccountInfo[i].account, i, handler);
+                }
+                deleteAccountStore(mContext, mAccountInfo[i].account, handler);
+                mAccountInfo[i].account.delete(mPreferences);
+                
+                // reset the progress indicator to mark account "complete" (in case est was wrong)
+                UpgradeAccounts.this.mHandler.setMaxProgress(i, 100);
+                UpgradeAccounts.this.mHandler.setProgress(i, 100);
+            }
+
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            if (!isCancelled()) {
+                // if there were no errors, we never enabled the OK button, but
+                // we'll just proceed through anyway and return to the Welcome activity
+                if (!mProceedButton.isEnabled()) {
+                    onClickOk();
+                }
+            }
+        }
+    }
+
+    /**
+     * Estimate the work required to convert an account.
+     * 1 (account) + # folders + # messages + # attachments
+     */
+    /* package */ static int estimateWork(Context context, Account account) {
+        int estimate = 1;         // account
+        try {
+            Store store = LocalStore.newInstance(account.getLocalStoreUri(), context, null);
+            Folder[] folders = store.getPersonalNamespaces();
+            estimate += folders.length;
+            for (int i = 0; i < folders.length; i++) {
+                Folder folder = folders[i];
+                folder.open(Folder.OpenMode.READ_ONLY, null);
+                estimate += folder.getMessageCount();
+            }
+            estimate += ((LocalStore)store).getStoredAttachmentCount();
+        
+        } catch (MessagingException e) {
+            Log.d(Email.LOG_TAG, "Exception while estimating account size " + e);
+        }
+        return estimate;
+    }
+
+    /**
+     * Clean out an IMAP account.  Anything we can reload from server, we delete.  This seems
+     * drastic, but it greatly reduces the risk of running out of disk space by copying everything.
+     */
+    /* package */ void cleanImapAccount(Context context, Account account, int accountNum,
+            UIHandler handler) {
+        String storeUri = account.getStoreUri();
+        if (!storeUri.startsWith(Store.STORE_SCHEME_IMAP)) {
+            return;
+        }
+        if (handler != null) {
+            handler.incProgress(accountNum);
+        }
+        
+    }
+    
+    /**
+     * Copy an account.
+     */
+    /* package */ void copyAccount(Context context, Account account, int accountNum,
+            UIHandler handler) {
+        // If already exists- just skip it
+        int existCount = EmailContent.count(context, EmailContent.Account.CONTENT_URI,
+                WHERE_ACCOUNT_UUID_IS, new String[] { account.getUuid() });
+        if (existCount > 0) {
+            Log.d(Email.LOG_TAG, "No conversion, account exists: " + account.getDescription());
+            if (handler != null) {
+                handler.error(context.getString(R.string.upgrade_accounts_error));
+            }
+            return;
+        }
+        // Create the new account and write it
+        EmailContent.Account newAccount = LegacyConversions.makeAccount(context, account);
+        newAccount.save(context);
+        if (handler != null) {
+            handler.incProgress(accountNum);
+        }
+        
+        // TODO folders
+        // TODO messages
+        // TODO attachments
+    }
+
+    /**
+     * Delete an account
+     */
+    /* package */ void deleteAccountStore(Context context, Account account, UIHandler handler) {
+        try {
+            Store store = LocalStore.newInstance(account.getLocalStoreUri(), context, null);
+            store.delete();
+        } catch (MessagingException e) {
+            Log.d(Email.LOG_TAG, "Exception while deleting account " + e);
+            if (handler != null) {
+                handler.error(context.getString(R.string.upgrade_accounts_error));
+            }
+        }
+    }
+
+}
diff --git a/src/com/android/email/activity/Welcome.java b/src/com/android/email/activity/Welcome.java
index 137b2a5..8978fac 100644
--- a/src/com/android/email/activity/Welcome.java
+++ b/src/com/android/email/activity/Welcome.java
@@ -16,13 +16,16 @@
 
 package com.android.email.activity;
 
+import com.android.email.Account;
 import com.android.email.AccountBackupRestore;
 import com.android.email.ExchangeUtils;
+import com.android.email.Preferences;
 import com.android.email.activity.setup.AccountSetupBasics;
-import com.android.email.provider.EmailContent.Account;
+import com.android.email.provider.EmailContent;
 import com.android.email.provider.EmailContent.Mailbox;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.os.Bundle;
@@ -39,6 +42,9 @@
  */
 public class Welcome extends Activity {
 
+    /** DO NOT CHECK IN AS 'TRUE' - DEVELOPMENT ONLY */
+    private static final boolean DEBUG_FORCE_UPGRADES = false;
+
     public static void actionStart(Activity fromActivity) {
         Intent i = new Intent(fromActivity, Welcome.class);
         fromActivity.startActivity(i);
@@ -48,6 +54,14 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        // Quickly check for bulk upgrades (from older app versions) and switch to the
+        // upgrade activity if necessary
+        if (bulkUpgradesRequired(this, Preferences.getPreferences(this))) {
+            UpgradeAccounts.actionStart(this);
+            finish();
+            return;
+        }
+
         // Restore accounts, if it has not happened already
         // NOTE:  This is blocking, which it should not be (in the UI thread)
         // We're going to live with this for the short term and replace with something
@@ -65,8 +79,8 @@
         Cursor c = null;
         try {
             c = getContentResolver().query(
-                    Account.CONTENT_URI,
-                    Account.ID_PROJECTION,
+                    EmailContent.Account.CONTENT_URI,
+                    EmailContent.Account.ID_PROJECTION,
                     null, null, null);
             switch (c.getCount()) {
                 case 0:
@@ -74,7 +88,7 @@
                     break;
                 case 1:
                     c.moveToFirst();
-                    long accountId = c.getLong(Account.CONTENT_ID_COLUMN);
+                    long accountId = c.getLong(EmailContent.Account.CONTENT_ID_COLUMN);
                     MessageList.actionHandleAccount(this, accountId, Mailbox.TYPE_INBOX);
                     break;
                 default:
@@ -90,4 +104,42 @@
         // In all cases, do not return to this activity
         finish();
     }
+
+    /**
+     * Test for bulk upgrades and return true if necessary
+     * 
+     * TODO should be in an AsyncTask since it has DB ops
+     *
+     * @return true if upgrades required (old accounts exit).  false otherwise.
+     */
+    /* package */ boolean bulkUpgradesRequired(Context context, Preferences preferences) {
+        if (DEBUG_FORCE_UPGRADES) {
+            // build at least one fake account
+            Account fake = new Account(this);
+            fake.setDescription("Fake Account");
+            fake.setEmail("user@gmail.com");
+            fake.setName("First Last");
+            fake.setSenderUri("smtp://user:password@smtp.gmail.com");
+            fake.setStoreUri("imap://user:password@imap.gmail.com");
+            fake.save(preferences);
+            return true;
+        }
+
+        // 1. Get list of legacy accounts and look for any non-backup entries
+        Account[] legacyAccounts = preferences.getAccounts();
+        if (legacyAccounts.length == 0) {
+            return false;
+        }
+
+        // 2. Look at the first legacy account and decide what to do
+        // We only need to look at the first:  If it's not a backup account, then it's a true
+        // legacy account, and there are one or more accounts needing upgrade.  If it is a backup
+        // account, then we know for sure that there are no legacy accounts (backup deletes all
+        // old accounts, and indicates that "modern" code has already run on this device.)
+        if (0 != (legacyAccounts[0].getBackupFlags() & Account.BACKUP_FLAGS_IS_BACKUP)) {
+            return false;
+        } else {
+            return true; 
+        }
+    }
 }
diff --git a/src/com/android/email/mail/store/LocalStore.java b/src/com/android/email/mail/store/LocalStore.java
index b3fab9e..a765e9d 100644
--- a/src/com/android/email/mail/store/LocalStore.java
+++ b/src/com/android/email/mail/store/LocalStore.java
@@ -366,6 +366,20 @@
     }
 
     /**
+     * Report # of attachments (for migration estimates only - catches all exceptions and
+     * just returns zero)
+     */
+    public int getStoredAttachmentCount() {
+        try{
+            File[] attachments = mAttachmentsDir.listFiles();
+            return attachments.length;
+        }
+        catch (Exception e) {
+            return 0;
+        }
+    }
+
+    /**
      * Deletes all cached attachments for the entire store.
      */
     public void pruneCachedAttachments() throws MessagingException {