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