Home | History | Annotate | Download | only in activity
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.email.activity;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.net.Uri;
     23 import android.os.Bundle;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 import android.view.LayoutInflater;
     27 import android.view.Menu;
     28 import android.view.MenuItem;
     29 import android.view.View;
     30 import android.view.ViewGroup.LayoutParams;
     31 
     32 import com.android.email.Email;
     33 import com.android.email.Preferences;
     34 import com.android.email.R;
     35 import com.android.email.activity.setup.AccountSettings;
     36 import com.android.email.activity.setup.AccountSetupBasics;
     37 import com.android.email.service.EmailServiceUtils;
     38 import com.android.email.service.MailService;
     39 import com.android.emailcommon.Logging;
     40 import com.android.emailcommon.provider.Account;
     41 import com.android.emailcommon.provider.EmailContent;
     42 import com.android.emailcommon.provider.EmailContent.Message;
     43 import com.android.emailcommon.provider.Mailbox;
     44 import com.android.emailcommon.utility.EmailAsyncTask;
     45 import com.android.emailcommon.utility.IntentUtilities;
     46 import com.android.emailcommon.utility.Utility;
     47 import com.google.common.annotations.VisibleForTesting;
     48 
     49 /**
     50  * The Welcome activity initializes the application and starts {@link EmailActivity}, or launch
     51  * {@link AccountSetupBasics} if no accounts are configured.
     52  *
     53  * TOOD Show "your messages are on the way" message like gmail does during the inbox lookup.
     54  */
     55 public class Welcome extends Activity {
     56     /*
     57      * Commands for testing...
     58      *  Open 1 pane
     59         adb shell am start -a android.intent.action.MAIN \
     60             -d '"content://ui.email.android.com/view/mailbox"' \
     61             -e DEBUG_PANE_MODE 1
     62 
     63      *  Open 2 pane
     64         adb shell am start -a android.intent.action.MAIN \
     65             -d '"content://ui.email.android.com/view/mailbox"' \
     66             -e DEBUG_PANE_MODE 2
     67 
     68      *  Open an account (ID=1) in 2 pane
     69         adb shell am start -a android.intent.action.MAIN \
     70             -d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1"' \
     71             -e DEBUG_PANE_MODE 2
     72 
     73      *  Open a message (account id=1, mailbox id=2, message id=3)
     74         adb shell am start -a android.intent.action.MAIN \
     75             -d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1&MAILBOX_ID=2&MESSAGE_ID=3"' \
     76             -e DEBUG_PANE_MODE 2
     77 
     78      *  Open the combined starred on the combined view
     79         adb shell am start -a android.intent.action.MAIN \
     80             -d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1152921504606846976&MAILBOX_ID=-4"' \
     81             -e DEBUG_PANE_MODE 2
     82      */
     83 
     84     /**
     85      * Extra for debugging.  Set 1 to force one-pane.  Set 2 to force two-pane.
     86      */
     87     private static final String EXTRA_DEBUG_PANE_MODE = "DEBUG_PANE_MODE";
     88 
     89     private static final String VIEW_MAILBOX_INTENT_URL_PATH = "/view/mailbox";
     90 
     91     private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
     92 
     93     private View mWaitingForSyncView;
     94 
     95     private long mAccountId;
     96     private long mMailboxId;
     97     private long mMessageId;
     98     private String mAccountUuid;
     99 
    100     private MailboxFinder mInboxFinder;
    101 
    102     /**
    103      * Launch this activity.  Note:  It's assumed that this activity is only called as a means to
    104      * 'reset' the UI state; Because of this, it is always launched with FLAG_ACTIVITY_CLEAR_TOP,
    105      * which will drop any other activities on the stack (e.g. AccountFolderList or MessageList).
    106      */
    107     public static void actionStart(Activity fromActivity) {
    108         Intent i = IntentUtilities.createRestartAppIntent(fromActivity, Welcome.class);
    109         fromActivity.startActivity(i);
    110     }
    111 
    112     /**
    113      * Create an Intent to open email activity. If <code>accountId</code> is not -1, the
    114      * specified account will be automatically be opened when the activity starts.
    115      */
    116     public static Intent createOpenAccountInboxIntent(Context context, long accountId) {
    117         final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder(
    118                 VIEW_MAILBOX_INTENT_URL_PATH);
    119         IntentUtilities.setAccountId(b, accountId);
    120         return IntentUtilities.createRestartAppIntent(b.build());
    121     }
    122 
    123     /**
    124      * Create an Intent to open a message.
    125      */
    126     public static Intent createOpenMessageIntent(Context context, long accountId,
    127             long mailboxId, long messageId) {
    128         final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder(
    129                 VIEW_MAILBOX_INTENT_URL_PATH);
    130         IntentUtilities.setAccountId(b, accountId);
    131         IntentUtilities.setMailboxId(b, mailboxId);
    132         IntentUtilities.setMessageId(b, messageId);
    133         return IntentUtilities.createRestartAppIntent(b.build());
    134     }
    135 
    136     /**
    137      * Open account's inbox.
    138      */
    139     public static void actionOpenAccountInbox(Activity fromActivity, long accountId) {
    140         fromActivity.startActivity(createOpenAccountInboxIntent(fromActivity, accountId));
    141     }
    142 
    143     /**
    144      * Create an {@link Intent} for account shortcuts.  The returned intent stores the account's
    145      * UUID rather than the account ID, which will be changed after account restore.
    146      */
    147     public static Intent createAccountShortcutIntent(Context context, String uuid, long mailboxId) {
    148         final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder(
    149                 VIEW_MAILBOX_INTENT_URL_PATH);
    150         IntentUtilities.setAccountUuid(b, uuid);
    151         IntentUtilities.setMailboxId(b, mailboxId);
    152         return IntentUtilities.createRestartAppIntent(b.build());
    153     }
    154 
    155     /**
    156      * If the {@link #EXTRA_DEBUG_PANE_MODE} extra is "1" or "2", return 1 or 2 respectively.
    157      * Otherwise return 0.
    158      *
    159      * @see UiUtilities#setDebugPaneMode(int)
    160      * @see UiUtilities#useTwoPane(Context)
    161      */
    162     private static int getDebugPaneMode(Intent i) {
    163         Bundle extras = i.getExtras();
    164         if (extras != null) {
    165             String s = extras.getString(EXTRA_DEBUG_PANE_MODE);
    166             if ("1".equals(s)) {
    167                 return 1;
    168             } else if ("2".equals(s)) {
    169                 return 2;
    170             }
    171         }
    172         return 0;
    173     }
    174 
    175     @Override
    176     public void onCreate(Bundle icicle) {
    177         super.onCreate(icicle);
    178         ActivityHelper.debugSetWindowFlags(this);
    179 
    180         // Because the app could be reloaded (for debugging, etc.), we need to make sure that
    181         // ExchangeService gets a chance to start.  There is no harm to starting it if it has
    182         // already been started
    183         // When the service starts, it reconciles EAS accounts.
    184         // TODO More completely separate ExchangeService from Email app
    185         EmailServiceUtils.startExchangeService(this);
    186 
    187         // Extract parameters from the intent.
    188         final Intent intent = getIntent();
    189         mAccountId = IntentUtilities.getAccountIdFromIntent(intent);
    190         mMailboxId = IntentUtilities.getMailboxIdFromIntent(intent);
    191         mMessageId = IntentUtilities.getMessageIdFromIntent(intent);
    192         mAccountUuid = IntentUtilities.getAccountUuidFromIntent(intent);
    193         UiUtilities.setDebugPaneMode(getDebugPaneMode(intent));
    194 
    195         // Reconcile POP/IMAP accounts.  EAS accounts are taken care of by ExchangeService.
    196         EmailAsyncTask.runAsyncParallel(new Runnable() {
    197             @Override
    198             public void run() {
    199                 // Reconciling can be heavy - so do it in the background.
    200                 if (MailService.hasMismatchInPopImapAccounts(Welcome.this)) {
    201                     MailService.reconcilePopImapAccountsSync(Welcome.this);
    202                 }
    203                 Welcome.this.runOnUiThread(new Runnable() {
    204                     @Override
    205                     public void run() {
    206                         resolveAccount();
    207                     }});
    208             }
    209         });
    210 
    211         // Reset the "accounts changed" notification, now that we're here
    212         Email.setNotifyUiAccountsChanged(false);
    213     }
    214 
    215     @Override
    216     public boolean onCreateOptionsMenu(Menu menu) {
    217         // Only create the menu if we had to stop and show a loading spinner - otherwise
    218         // this is a transient activity with no UI.
    219         if (mInboxFinder == null) {
    220             return super.onCreateOptionsMenu(menu);
    221         }
    222 
    223         getMenuInflater().inflate(R.menu.welcome, menu);
    224         return true;
    225     }
    226 
    227     @Override
    228     public boolean onOptionsItemSelected(MenuItem item) {
    229         if (item.getItemId() == R.id.account_settings) {
    230             AccountSettings.actionSettings(this, mAccountId);
    231             return true;
    232         }
    233         return super.onOptionsItemSelected(item);
    234     }
    235 
    236     @Override
    237     protected void onStop() {
    238         // Cancel all running tasks.
    239         // (If it's stopping for configuration changes, we just re-do everything on the new
    240         // instance)
    241         stopInboxLookup();
    242         mTaskTracker.cancellAllInterrupt();
    243 
    244         super.onStop();
    245 
    246         if (!isChangingConfigurations()) {
    247             // This means the user opened some other app.
    248             // Just close self and not launch EmailActivity.
    249             if (Email.DEBUG && Logging.DEBUG_LIFECYCLE) {
    250                 Log.d(Logging.LOG_TAG, "Welcome: Closing self...");
    251             }
    252             finish();
    253         }
    254     }
    255 
    256     /**
    257      * {@inheritDoc}
    258      *
    259      * When launching an activity from {@link Welcome}, we always want to set
    260      * {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT}.
    261      */
    262     @Override
    263     public void startActivity(Intent intent) {
    264         intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    265         super.startActivity(intent);
    266     }
    267 
    268     /**
    269      * Stop inbox lookup.  This MSUT be called on the UI thread.
    270      */
    271     private void stopInboxLookup() {
    272         if (mInboxFinder != null) {
    273             mInboxFinder.cancel();
    274             mInboxFinder = null;
    275         }
    276     }
    277 
    278     /**
    279      * Start inbox lookup.  This MSUT be called on the UI thread.
    280      */
    281     private void startInboxLookup() {
    282         Log.i(Logging.LOG_TAG, "Inbox not found.  Starting mailbox finder...");
    283         stopInboxLookup(); // Stop if already running -- it shouldn't be but just in case.
    284         mInboxFinder = new MailboxFinder(this, mAccountId, Mailbox.TYPE_INBOX,
    285                 mMailboxFinderCallback);
    286         mInboxFinder.startLookup();
    287 
    288         // Show "your email will appear shortly" message.
    289         mWaitingForSyncView = LayoutInflater.from(this).inflate(
    290                 R.layout.waiting_for_sync_message, null);
    291         addContentView(mWaitingForSyncView, new LayoutParams(
    292                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    293         invalidateOptionsMenu();
    294     }
    295 
    296     /**
    297      * Determine which account to open with the given account ID and UUID.
    298      *
    299      * @return ID of the account to use.
    300      */
    301     @VisibleForTesting
    302     static long resolveAccountId(Context context, long inputAccountId, String inputUuid) {
    303         final long accountId;
    304 
    305         if (!TextUtils.isEmpty(inputUuid)) {
    306             // If a UUID is specified, try to use it.
    307             // If the UUID is invalid, accountId will be NO_ACCOUNT.
    308             accountId = Account.getAccountIdFromUuid(context, inputUuid);
    309 
    310         } else if (inputAccountId != Account.NO_ACCOUNT) {
    311             // If a valid account ID is specified, just use it.
    312             if (inputAccountId == Account.ACCOUNT_ID_COMBINED_VIEW
    313                     || Account.isValidId(context, inputAccountId)) {
    314                 accountId = inputAccountId;
    315             } else {
    316                 accountId = Account.NO_ACCOUNT;
    317             }
    318         } else {
    319             // Neither an accountID or a UUID is specified.
    320             // Use the last account used, falling back to the default.
    321             long lastUsedId = Preferences.getPreferences(context).getLastUsedAccountId();
    322             if (lastUsedId != Account.NO_ACCOUNT) {
    323                 if (!Account.isValidId(context, lastUsedId)) {
    324                     // The last account that was used has since been deleted.
    325                     lastUsedId = Account.NO_ACCOUNT;
    326                     Preferences.getPreferences(context).setLastUsedAccountId(Account.NO_ACCOUNT);
    327                 }
    328             }
    329             accountId = (lastUsedId == Account.NO_ACCOUNT)
    330                     ? Account.getDefaultAccountId(context)
    331                     : lastUsedId;
    332         }
    333         if (accountId != Account.NO_ACCOUNT) {
    334             // Okay, the given account is valid.
    335             return accountId;
    336         } else {
    337             // No, it's invalid.  Show the warning toast and use the default.
    338             Utility.showToast(context, R.string.toast_account_not_found);
    339             return Account.getDefaultAccountId(context);
    340         }
    341     }
    342 
    343     /**
    344      * Determine which account to use according to the number of accounts already set up,
    345      * {@link #mAccountId} and {@link #mAccountUuid}.
    346      *
    347      * <pre>
    348      * 1. If there's no account configured, start account setup.
    349      * 2. Otherwise detemine which account to open with {@link #resolveAccountId} and
    350      *   2a. If the account doesn't have inbox yet, start inbox finder.
    351      *   2b. Otherwise open the main activity.
    352      * </pre>
    353      */
    354     private void resolveAccount() {
    355         final int numAccount = EmailContent.count(this, Account.CONTENT_URI);
    356         if (numAccount == 0) {
    357             AccountSetupBasics.actionNewAccount(this);
    358             finish();
    359             return;
    360         } else {
    361             mAccountId = resolveAccountId(this, mAccountId, mAccountUuid);
    362             if (Account.isNormalAccount(mAccountId) &&
    363                     Mailbox.findMailboxOfType(this, mAccountId, Mailbox.TYPE_INBOX)
    364                             == Mailbox.NO_MAILBOX) {
    365                 startInboxLookup();
    366                 return;
    367             }
    368         }
    369         startEmailActivity();
    370     }
    371 
    372     /**
    373      * Start {@link EmailActivity} using {@link #mAccountId}, {@link #mMailboxId} and
    374      * {@link #mMessageId}.
    375      */
    376     private void startEmailActivity() {
    377         final Intent i;
    378         if (mMessageId != Message.NO_MESSAGE) {
    379             i = EmailActivity.createOpenMessageIntent(this, mAccountId, mMailboxId, mMessageId);
    380         } else if (mMailboxId != Mailbox.NO_MAILBOX) {
    381             i = EmailActivity.createOpenMailboxIntent(this, mAccountId, mMailboxId);
    382         } else {
    383             i = EmailActivity.createOpenAccountIntent(this, mAccountId);
    384         }
    385         startActivity(i);
    386         finish();
    387     }
    388 
    389     private final MailboxFinder.Callback mMailboxFinderCallback = new MailboxFinder.Callback() {
    390         // This MUST be called from callback methods.
    391         private void cleanUp() {
    392             mInboxFinder = null;
    393         }
    394 
    395         @Override
    396         public void onAccountNotFound() {
    397             cleanUp();
    398             // Account removed?  Clear the IDs and restart the task.  Which will result in either
    399             // a) show account setup if there's really no accounts  or b) open the default account.
    400 
    401             mAccountId = Account.NO_ACCOUNT;
    402             mMailboxId = Mailbox.NO_MAILBOX;
    403             mMessageId = Message.NO_MESSAGE;
    404             mAccountUuid = null;
    405 
    406             // Restart the account resolution.
    407             resolveAccount();
    408         }
    409 
    410         @Override
    411         public void onMailboxNotFound(long accountId) {
    412             // Just do the same thing as "account not found".
    413             onAccountNotFound();
    414         }
    415 
    416         @Override
    417         public void onAccountSecurityHold(long accountId) {
    418             cleanUp();
    419 
    420             ActivityHelper.showSecurityHoldDialog(Welcome.this, accountId);
    421             finish();
    422         }
    423 
    424         @Override
    425         public void onMailboxFound(long accountId, long mailboxId) {
    426             cleanUp();
    427 
    428             // Okay the account has Inbox now.  Start the main activity.
    429             startEmailActivity();
    430         }
    431     };
    432 }
    433