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         if (MailService.hasMismatchInPopImapAccounts(this)) {
    197             EmailAsyncTask.runAsyncParallel(new Runnable() {
    198                 @Override
    199                 public void run() {
    200                     // Reconciling can be heavy - so do it in the background.
    201                     MailService.reconcilePopImapAccountsSync(Welcome.this);
    202                     resolveAccount();
    203                 }
    204             });
    205         } else {
    206             resolveAccount();
    207         }
    208 
    209         // Reset the "accounts changed" notification, now that we're here
    210         Email.setNotifyUiAccountsChanged(false);
    211     }
    212 
    213     @Override
    214     public boolean onCreateOptionsMenu(Menu menu) {
    215         // Only create the menu if we had to stop and show a loading spinner - otherwise
    216         // this is a transient activity with no UI.
    217         if (mInboxFinder == null) {
    218             return super.onCreateOptionsMenu(menu);
    219         }
    220 
    221         getMenuInflater().inflate(R.menu.welcome, menu);
    222         return true;
    223     }
    224 
    225     @Override
    226     public boolean onOptionsItemSelected(MenuItem item) {
    227         if (item.getItemId() == R.id.account_settings) {
    228             AccountSettings.actionSettings(this, mAccountId);
    229             return true;
    230         }
    231         return super.onOptionsItemSelected(item);
    232     }
    233 
    234     @Override
    235     protected void onStop() {
    236         // Cancel all running tasks.
    237         // (If it's stopping for configuration changes, we just re-do everything on the new
    238         // instance)
    239         stopInboxLookup();
    240         mTaskTracker.cancellAllInterrupt();
    241 
    242         super.onStop();
    243 
    244         if (!isChangingConfigurations()) {
    245             // This means the user opened some other app.
    246             // Just close self and not launch EmailActivity.
    247             if (Email.DEBUG && Logging.DEBUG_LIFECYCLE) {
    248                 Log.d(Logging.LOG_TAG, "Welcome: Closing self...");
    249             }
    250             finish();
    251         }
    252     }
    253 
    254     /**
    255      * {@inheritDoc}
    256      *
    257      * When launching an activity from {@link Welcome}, we always want to set
    258      * {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT}.
    259      */
    260     @Override
    261     public void startActivity(Intent intent) {
    262         intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    263         super.startActivity(intent);
    264     }
    265 
    266     /**
    267      * Stop inbox lookup.  This MSUT be called on the UI thread.
    268      */
    269     private void stopInboxLookup() {
    270         if (mInboxFinder != null) {
    271             mInboxFinder.cancel();
    272             mInboxFinder = null;
    273         }
    274     }
    275 
    276     /**
    277      * Start inbox lookup.  This MSUT be called on the UI thread.
    278      */
    279     private void startInboxLookup() {
    280         Log.i(Logging.LOG_TAG, "Inbox not found.  Starting mailbox finder...");
    281         stopInboxLookup(); // Stop if already running -- it shouldn't be but just in case.
    282         mInboxFinder = new MailboxFinder(this, mAccountId, Mailbox.TYPE_INBOX,
    283                 mMailboxFinderCallback);
    284         mInboxFinder.startLookup();
    285 
    286         // Show "your email will appear shortly" message.
    287         mWaitingForSyncView = LayoutInflater.from(this).inflate(
    288                 R.layout.waiting_for_sync_message, null);
    289         addContentView(mWaitingForSyncView, new LayoutParams(
    290                 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    291         invalidateOptionsMenu();
    292     }
    293 
    294     /**
    295      * Determine which account to open with the given account ID and UUID.
    296      *
    297      * @return ID of the account to use.
    298      */
    299     @VisibleForTesting
    300     static long resolveAccountId(Context context, long inputAccountId, String inputUuid) {
    301         final long accountId;
    302 
    303         if (!TextUtils.isEmpty(inputUuid)) {
    304             // If a UUID is specified, try to use it.
    305             // If the UUID is invalid, accountId will be NO_ACCOUNT.
    306             accountId = Account.getAccountIdFromUuid(context, inputUuid);
    307 
    308         } else if (inputAccountId != Account.NO_ACCOUNT) {
    309             // If a valid account ID is specified, just use it.
    310             if (inputAccountId == Account.ACCOUNT_ID_COMBINED_VIEW
    311                     || Account.isValidId(context, inputAccountId)) {
    312                 accountId = inputAccountId;
    313             } else {
    314                 accountId = Account.NO_ACCOUNT;
    315             }
    316         } else {
    317             // Neither an accountID or a UUID is specified.
    318             // Use the last account used, falling back to the default.
    319             long lastUsedId = Preferences.getPreferences(context).getLastUsedAccountId();
    320             if (lastUsedId != Account.NO_ACCOUNT) {
    321                 if (!Account.isValidId(context, lastUsedId)) {
    322                     // The last account that was used has since been deleted.
    323                     lastUsedId = Account.NO_ACCOUNT;
    324                     Preferences.getPreferences(context).setLastUsedAccountId(Account.NO_ACCOUNT);
    325                 }
    326             }
    327             accountId = (lastUsedId == Account.NO_ACCOUNT)
    328                     ? Account.getDefaultAccountId(context)
    329                     : lastUsedId;
    330         }
    331         if (accountId != Account.NO_ACCOUNT) {
    332             // Okay, the given account is valid.
    333             return accountId;
    334         } else {
    335             // No, it's invalid.  Show the warning toast and use the default.
    336             Utility.showToast(context, R.string.toast_account_not_found);
    337             return Account.getDefaultAccountId(context);
    338         }
    339     }
    340 
    341     /**
    342      * Determine which account to use according to the number of accounts already set up,
    343      * {@link #mAccountId} and {@link #mAccountUuid}.
    344      *
    345      * <pre>
    346      * 1. If there's no account configured, start account setup.
    347      * 2. Otherwise detemine which account to open with {@link #resolveAccountId} and
    348      *   2a. If the account doesn't have inbox yet, start inbox finder.
    349      *   2b. Otherwise open the main activity.
    350      * </pre>
    351      */
    352     private void resolveAccount() {
    353         final int numAccount = EmailContent.count(this, Account.CONTENT_URI);
    354         if (numAccount == 0) {
    355             AccountSetupBasics.actionNewAccount(this);
    356             finish();
    357             return;
    358         } else {
    359             mAccountId = resolveAccountId(this, mAccountId, mAccountUuid);
    360             if (Account.isNormalAccount(mAccountId) &&
    361                     Mailbox.findMailboxOfType(this, mAccountId, Mailbox.TYPE_INBOX)
    362                             == Mailbox.NO_MAILBOX) {
    363                 startInboxLookup();
    364                 return;
    365             }
    366         }
    367         startEmailActivity();
    368     }
    369 
    370     /**
    371      * Start {@link EmailActivity} using {@link #mAccountId}, {@link #mMailboxId} and
    372      * {@link #mMessageId}.
    373      */
    374     private void startEmailActivity() {
    375         final Intent i;
    376         if (mMessageId != Message.NO_MESSAGE) {
    377             i = EmailActivity.createOpenMessageIntent(this, mAccountId, mMailboxId, mMessageId);
    378         } else if (mMailboxId != Mailbox.NO_MAILBOX) {
    379             i = EmailActivity.createOpenMailboxIntent(this, mAccountId, mMailboxId);
    380         } else {
    381             i = EmailActivity.createOpenAccountIntent(this, mAccountId);
    382         }
    383         startActivity(i);
    384         finish();
    385     }
    386 
    387     private final MailboxFinder.Callback mMailboxFinderCallback = new MailboxFinder.Callback() {
    388         // This MUST be called from callback methods.
    389         private void cleanUp() {
    390             mInboxFinder = null;
    391         }
    392 
    393         @Override
    394         public void onAccountNotFound() {
    395             cleanUp();
    396             // Account removed?  Clear the IDs and restart the task.  Which will result in either
    397             // a) show account setup if there's really no accounts  or b) open the default account.
    398 
    399             mAccountId = Account.NO_ACCOUNT;
    400             mMailboxId = Mailbox.NO_MAILBOX;
    401             mMessageId = Message.NO_MESSAGE;
    402             mAccountUuid = null;
    403 
    404             // Restart the account resolution.
    405             resolveAccount();
    406         }
    407 
    408         @Override
    409         public void onMailboxNotFound(long accountId) {
    410             // Just do the same thing as "account not found".
    411             onAccountNotFound();
    412         }
    413 
    414         @Override
    415         public void onAccountSecurityHold(long accountId) {
    416             cleanUp();
    417 
    418             ActivityHelper.showSecurityHoldDialog(Welcome.this, accountId);
    419             finish();
    420         }
    421 
    422         @Override
    423         public void onMailboxFound(long accountId, long mailboxId) {
    424             cleanUp();
    425 
    426             // Okay the account has Inbox now.  Start the main activity.
    427             startEmailActivity();
    428         }
    429     };
    430 }
    431