Welcome.java revision 95ad0f0e93d290b2c630a49a254e61f10b2783c8
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
17package com.android.email.activity;
18
19import com.android.email.Email;
20import com.android.email.ExchangeUtils;
21import com.android.email.R;
22import com.android.email.activity.setup.AccountSecurity;
23import com.android.email.activity.setup.AccountSetupBasics;
24import com.android.email.service.MailService;
25import com.android.emailcommon.Logging;
26import com.android.emailcommon.provider.Account;
27import com.android.emailcommon.provider.EmailContent;
28import com.android.emailcommon.provider.EmailContent.Message;
29import com.android.emailcommon.provider.Mailbox;
30import com.android.emailcommon.utility.EmailAsyncTask;
31import com.android.emailcommon.utility.Utility;
32import com.google.common.annotations.VisibleForTesting;
33
34import android.app.Activity;
35import android.content.Context;
36import android.content.Intent;
37import android.net.Uri;
38import android.os.Bundle;
39import android.text.TextUtils;
40import android.util.Log;
41
42/**
43 * The Welcome activity initializes the application and starts {@link EmailActivity}, or launch
44 * {@link AccountSetupBasics} if no accounts are configured.
45 *
46 * TOOD Show "your messages are on the way" message like gmail does during the inbox lookup.
47 */
48public class Welcome extends Activity {
49    /*
50     * Commands for testing...
51     *  Open 1 pane
52        adb shell am start -a android.intent.action.MAIN \
53            -d '"content://ui.email.android.com/view/mailbox"' \
54            -e DEBUG_PANE_MODE 1
55
56     *  Open 2 pane
57        adb shell am start -a android.intent.action.MAIN \
58            -d '"content://ui.email.android.com/view/mailbox"' \
59            -e DEBUG_PANE_MODE 2
60
61     *  Open an account (ID=1) in 2 pane
62        adb shell am start -a android.intent.action.MAIN \
63            -d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1"' \
64            -e DEBUG_PANE_MODE 2
65
66     *  Open a message (account id=1, mailbox id=2, message id=3)
67        adb shell am start -a android.intent.action.MAIN \
68            -d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1&MAILBOX_ID=2&MESSAGE_ID=3"' \
69            -e DEBUG_PANE_MODE 2
70
71     *  Open the combined starred on the combined view
72        adb shell am start -a android.intent.action.MAIN \
73            -d '"content://ui.email.android.com/view/mailbox?ACCOUNT_ID=1152921504606846976&MAILBOX_ID=-4"' \
74            -e DEBUG_PANE_MODE 2
75     */
76
77    /**
78     * Extra for debugging.  Set 1 to force one-pane.  Set 2 to force two-pane.
79     */
80    private static final String EXTRA_DEBUG_PANE_MODE = "DEBUG_PANE_MODE";
81
82    private static final String VIEW_MAILBOX_INTENT_URL_PATH = "/view/mailbox";
83
84    private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
85
86    // Account reconciler is started from AccountResolver, which we may run multiple times,
87    // so remember if we did it already to prevent from running it twice.
88    private boolean mAccountsReconciled;
89
90    private long mAccountId;
91    private long mMailboxId;
92    private long mMessageId;
93    private String mAccountUuid;
94
95    private MailboxFinder mInboxFinder;
96
97    /**
98     * Launch this activity.  Note:  It's assumed that this activity is only called as a means to
99     * 'reset' the UI state; Because of this, it is always launched with FLAG_ACTIVITY_CLEAR_TOP,
100     * which will drop any other activities on the stack (e.g. AccountFolderList or MessageList).
101     */
102    public static void actionStart(Activity fromActivity) {
103        Intent i = IntentUtilities.createRestartAppIntent(fromActivity, Welcome.class);
104        fromActivity.startActivity(i);
105    }
106
107    /**
108     * Create an Intent to open email activity. If <code>accountId</code> is not -1, the
109     * specified account will be automatically be opened when the activity starts.
110     */
111    public static Intent createOpenAccountInboxIntent(Context context, long accountId) {
112        final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder(
113                VIEW_MAILBOX_INTENT_URL_PATH);
114        IntentUtilities.setAccountId(b, accountId);
115        return IntentUtilities.createRestartAppIntent(b.build());
116    }
117
118    /**
119     * Create an Intent to open a message.
120     */
121    public static Intent createOpenMessageIntent(Context context, long accountId,
122            long mailboxId, long messageId) {
123        final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder(
124                VIEW_MAILBOX_INTENT_URL_PATH);
125        IntentUtilities.setAccountId(b, accountId);
126        IntentUtilities.setMailboxId(b, mailboxId);
127        IntentUtilities.setMessageId(b, messageId);
128        return IntentUtilities.createRestartAppIntent(b.build());
129    }
130
131    /**
132     * Open account's inbox.
133     */
134    public static void actionOpenAccountInbox(Activity fromActivity, long accountId) {
135        fromActivity.startActivity(createOpenAccountInboxIntent(fromActivity, accountId));
136    }
137
138    /**
139     * Create an {@link Intent} for account shortcuts.  The returned intent stores the account's
140     * UUID rather than the account ID, which will be changed after account restore.
141     */
142    public static Intent createAccountShortcutIntent(Context context, String uuid, long mailboxId) {
143        final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder(
144                VIEW_MAILBOX_INTENT_URL_PATH);
145        IntentUtilities.setAccountUuid(b, uuid);
146        IntentUtilities.setMailboxId(b, mailboxId);
147        return IntentUtilities.createRestartAppIntent(b.build());
148    }
149
150    /**
151     * If the {@link #EXTRA_DEBUG_PANE_MODE} extra is "1" or "2", return 1 or 2 respectively.
152     * Otherwise return 0.
153     *
154     * @see UiUtilities#setDebugPaneMode(int)
155     * @see UiUtilities#useTwoPane(Context)
156     */
157    private static int getDebugPaneMode(Intent i) {
158        Bundle extras = i.getExtras();
159        if (extras != null) {
160            String s = extras.getString(EXTRA_DEBUG_PANE_MODE);
161            if ("1".equals(s)) {
162                return 1;
163            } else if ("2".equals(s)) {
164                return 2;
165            }
166        }
167        return 0;
168    }
169
170    @Override
171    public void onCreate(Bundle icicle) {
172        super.onCreate(icicle);
173        ActivityHelper.debugSetWindowFlags(this);
174
175        // Reset the "accounts changed" notification, now that we're here
176        Email.setNotifyUiAccountsChanged(false);
177
178        // Because the app could be reloaded (for debugging, etc.), we need to make sure that
179        // ExchangeService gets a chance to start.  There is no harm to starting it if it has
180        // already been started
181        // When the service starts, it reconciles EAS accounts.
182        // TODO More completely separate ExchangeService from Email app
183        ExchangeUtils.startExchangeService(this);
184
185        // Extract parameters from the intent.
186        final Intent intent = getIntent();
187        mAccountId = IntentUtilities.getAccountIdFromIntent(intent);
188        mMailboxId = IntentUtilities.getMailboxIdFromIntent(intent);
189        mMessageId = IntentUtilities.getMessageIdFromIntent(intent);
190        mAccountUuid = IntentUtilities.getAccountUuidFromIntent(intent);
191        UiUtilities.setDebugPaneMode(getDebugPaneMode(intent));
192
193        startAccountResolver();
194    }
195
196    @Override
197    protected void onStop() {
198        // The activity no longer visible, which means the user opened some other app.
199        // Just close self and not launch EmailActivity.
200        stopInboxLookup();
201        mTaskTracker.cancellAllInterrupt();
202        super.onStop();
203        finish();
204    }
205
206    /**
207     * {@inheritDoc}
208     *
209     * When launching an activity from {@link Welcome}, we always want to set
210     * {@link Intent#FLAG_ACTIVITY_FORWARD_RESULT}.
211     */
212    @Override
213    public void startActivity(Intent intent) {
214        intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
215        super.startActivity(intent);
216    }
217
218    private void startAccountResolver() {
219        new AccountResolver().executeParallel();
220    }
221
222    /**
223     * Stop inbox lookup.  This MSUT be called on the UI thread.
224     */
225    private void stopInboxLookup() {
226        if (mInboxFinder != null) {
227            mInboxFinder.cancel();
228            mInboxFinder = null;
229        }
230    }
231
232    /**
233     * Start inbox lookup.  This MSUT be called on the UI thread.
234     */
235    private void startInboxLookup() {
236        Log.i(Logging.LOG_TAG, "Inbox not found.  Starting mailbox finder...");
237        stopInboxLookup(); // Stop if already running -- it shouldn't be but just in case.
238        mInboxFinder = new MailboxFinder(this, mAccountId, Mailbox.TYPE_INBOX,
239                mMailboxFinderCallback);
240        mInboxFinder.startLookup();
241    }
242
243    /**
244     * Determine which account to open with the given account ID and UUID.
245     *
246     * @return ID of the account to use.
247     */
248    @VisibleForTesting
249    static long resolveAccountId(Context context, long inputAccountId, String inputUuid) {
250        final long accountId;
251
252        if (!TextUtils.isEmpty(inputUuid)) {
253            // If a UUID is specified, try to use it.
254            // If the UUID is invalid, accountId will be NO_ACCOUNT.
255            accountId = Account.getAccountIdFromUuid(context, inputUuid);
256
257        } else if (inputAccountId != Account.NO_ACCOUNT) {
258            // If a valid account ID is specified, just use it.
259            if (inputAccountId == Account.ACCOUNT_ID_COMBINED_VIEW
260                    || Account.isValidId(context, inputAccountId)) {
261                accountId = inputAccountId;
262            } else {
263                accountId = Account.NO_ACCOUNT;
264            }
265        } else {
266            // Neither an accountID or a UUID is specified.
267            // Use the default, without showing the "account removed?" toast.
268            accountId = Account.getDefaultAccountId(context);
269        }
270        if (accountId != Account.NO_ACCOUNT) {
271            // Okay, the given account is valid.
272            return accountId;
273        } else {
274            // No, it's invalid.  Show the warning toast and use the default.
275            Utility.showToast(context, R.string.toast_account_not_found);
276            return Account.getDefaultAccountId(context);
277        }
278    }
279
280    /**
281     * Determine which account to use according to the number of accounts already set up,
282     * {@link #mAccountId} and {@link #mAccountUuid}.
283     *
284     * <pre>
285     * 1. If there's no account configured, start account setup.
286     * 2. Otherwise detemine which account to open with {@link #resolveAccountId} and
287     *   2a. If the account doesn't have inbox yet, start inbox finder.
288     *   2b. Otherwise open the main activity.
289     * </pre>
290     */
291    private class AccountResolver extends EmailAsyncTask<Void, Void, Void> {
292        private boolean mStartAccountSetup;
293        private boolean mStartInboxLookup;
294
295        public AccountResolver() {
296            super(mTaskTracker);
297        }
298
299        @Override
300        protected Void doInBackground(Void... params) {
301            final Activity activity = Welcome.this;
302
303            if (!mAccountsReconciled) {
304                mAccountsReconciled = true;
305
306                // Reconcile POP/IMAP accounts.  EAS accounts are taken care of by ExchangeService.
307                //
308                // TODO Do we still really have to do it at startup?
309                //      Now that we use the broadcast to detect system account changes, our database
310                //      should always be in sync with the system accounts...
311                MailService.reconcilePopImapAccountsSync(activity);
312            }
313
314            final int numAccount = EmailContent.count(activity, Account.CONTENT_URI);
315            if (numAccount == 0) {
316                mStartAccountSetup = true;
317            } else {
318                mAccountId = resolveAccountId(activity, mAccountId, mAccountUuid);
319                if (Account.isNormalAccount(mAccountId) &&
320                        Mailbox.findMailboxOfType(activity, mAccountId, Mailbox.TYPE_INBOX)
321                        == Mailbox.NO_MAILBOX) {
322                    mStartInboxLookup = true;
323                }
324            }
325            return null;
326        }
327
328        @Override
329        protected void onPostExecute(Void noResult) {
330            final Activity activity = Welcome.this;
331
332            if (mStartAccountSetup) {
333                AccountSetupBasics.actionNewAccount(activity);
334                activity.finish();
335            } else if (mStartInboxLookup) {
336                startInboxLookup();
337            } else {
338                startEmailActivity();
339            }
340        }
341    }
342
343    /**
344     * Start {@link EmailActivity} using {@link #mAccountId}, {@link #mMailboxId} and
345     * {@link #mMessageId}.
346     */
347    private void startEmailActivity() {
348        final Intent i;
349        if (mMessageId != Message.NO_MESSAGE) {
350            i = EmailActivity.createOpenMessageIntent(this, mAccountId, mMailboxId, mMessageId);
351        } else if (mMailboxId != Mailbox.NO_MAILBOX) {
352            i = EmailActivity.createOpenMailboxIntent(this, mAccountId, mMailboxId);
353        } else {
354            i = EmailActivity.createOpenAccountIntent(this, mAccountId);
355        }
356        startActivity(i);
357        finish();
358    }
359
360    private final MailboxFinder.Callback mMailboxFinderCallback = new MailboxFinder.Callback() {
361        // This MUST be called from callback methods.
362        private void cleanUp() {
363            mInboxFinder = null;
364        }
365
366        @Override
367        public void onAccountNotFound() {
368            cleanUp();
369            // Account removed?  Clear the IDs and restart the task.  Which will result in either
370            // a) show account setup if there's really no accounts  or b) open the default account.
371
372            mAccountId = Account.NO_ACCOUNT;
373            mMailboxId = Mailbox.NO_MAILBOX;
374            mMessageId = Message.NO_MESSAGE;
375            mAccountUuid = null;
376
377            // Restart the task.
378            startAccountResolver();
379        }
380
381        @Override
382        public void onMailboxNotFound(long accountId) {
383            // Just do the same thing as "account not found".
384            onAccountNotFound();
385        }
386
387        @Override
388        public void onAccountSecurityHold(long accountId) {
389            cleanUp();
390
391            startActivity(
392                    AccountSecurity.actionUpdateSecurityIntent(Welcome.this, accountId, true));
393            finish();
394        }
395
396        @Override
397        public void onMailboxFound(long accountId, long mailboxId) {
398            cleanUp();
399
400            // Okay the account has Inbox now.  Start the main activity.
401            startEmailActivity();
402        }
403    };
404}
405