• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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.app.Fragment;
21 import android.content.Intent;
22 import android.os.Bundle;
23 import android.os.Handler;
24 import android.text.TextUtils;
25 import android.util.Log;
26 import android.view.Menu;
27 import android.view.MenuItem;
28 import android.view.View;
29 import android.widget.TextView;
30 
31 import com.android.email.Controller;
32 import com.android.email.ControllerResultUiThreadWrapper;
33 import com.android.email.Email;
34 import com.android.email.MessageListContext;
35 import com.android.email.MessagingExceptionStrings;
36 import com.android.email.R;
37 import com.android.emailcommon.Logging;
38 import com.android.emailcommon.mail.MessagingException;
39 import com.android.emailcommon.provider.Account;
40 import com.android.emailcommon.provider.EmailContent.Message;
41 import com.android.emailcommon.provider.Mailbox;
42 import com.android.emailcommon.utility.EmailAsyncTask;
43 import com.android.emailcommon.utility.IntentUtilities;
44 import com.google.common.base.Preconditions;
45 
46 import java.util.ArrayList;
47 
48 /**
49  * The main Email activity, which is used on both the tablet and the phone.
50  *
51  * Because this activity is device agnostic, so most of the UI aren't owned by this, but by
52  * the UIController.
53  */
54 public class EmailActivity extends Activity implements View.OnClickListener, FragmentInstallable {
55     public static final String EXTRA_ACCOUNT_ID = "ACCOUNT_ID";
56     public static final String EXTRA_MAILBOX_ID = "MAILBOX_ID";
57     public static final String EXTRA_MESSAGE_ID = "MESSAGE_ID";
58     public static final String EXTRA_QUERY_STRING = "QUERY_STRING";
59 
60     /** Loader IDs starting with this is safe to use from UIControllers. */
61     static final int UI_CONTROLLER_LOADER_ID_BASE = 100;
62 
63     /** Loader IDs starting with this is safe to use from ActionBarController. */
64     static final int ACTION_BAR_CONTROLLER_LOADER_ID_BASE = 200;
65 
66     private Controller mController;
67     private Controller.Result mControllerResult;
68 
69     private UIControllerBase mUIController;
70 
71     private final EmailAsyncTask.Tracker mTaskTracker = new EmailAsyncTask.Tracker();
72 
73     /** Banner to display errors */
74     private BannerController mErrorBanner;
75     /** Id of the account that had a messaging exception most recently. */
76     private long mLastErrorAccountId;
77 
78     /**
79      * Create an intent to launch and open account's inbox.
80      *
81      * @param accountId If -1, default account will be used.
82      */
createOpenAccountIntent(Activity fromActivity, long accountId)83     public static Intent createOpenAccountIntent(Activity fromActivity, long accountId) {
84         Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class);
85         if (accountId != -1) {
86             i.putExtra(EXTRA_ACCOUNT_ID, accountId);
87         }
88         return i;
89     }
90 
91     /**
92      * Create an intent to launch and open a mailbox.
93      *
94      * @param accountId must not be -1.
95      * @param mailboxId must not be -1.  Magic mailboxes IDs (such as
96      * {@link Mailbox#QUERY_ALL_INBOXES}) don't work.
97      */
createOpenMailboxIntent(Activity fromActivity, long accountId, long mailboxId)98     public static Intent createOpenMailboxIntent(Activity fromActivity, long accountId,
99             long mailboxId) {
100         if (accountId == -1 || mailboxId == -1) {
101             throw new IllegalArgumentException();
102         }
103         Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class);
104         i.putExtra(EXTRA_ACCOUNT_ID, accountId);
105         i.putExtra(EXTRA_MAILBOX_ID, mailboxId);
106         return i;
107     }
108 
109     /**
110      * Create an intent to launch and open a message.
111      *
112      * @param accountId must not be -1.
113      * @param mailboxId must not be -1.  Magic mailboxes IDs (such as
114      * {@link Mailbox#QUERY_ALL_INBOXES}) don't work.
115      * @param messageId must not be -1.
116      */
createOpenMessageIntent(Activity fromActivity, long accountId, long mailboxId, long messageId)117     public static Intent createOpenMessageIntent(Activity fromActivity, long accountId,
118             long mailboxId, long messageId) {
119         if (accountId == -1 || mailboxId == -1 || messageId == -1) {
120             throw new IllegalArgumentException();
121         }
122         Intent i = IntentUtilities.createRestartAppIntent(fromActivity, EmailActivity.class);
123         i.putExtra(EXTRA_ACCOUNT_ID, accountId);
124         i.putExtra(EXTRA_MAILBOX_ID, mailboxId);
125         i.putExtra(EXTRA_MESSAGE_ID, messageId);
126         return i;
127     }
128 
129     /**
130      * Create an intent to launch search activity.
131      *
132      * @param accountId ID of the account for the mailbox.  Must not be {@link Account#NO_ACCOUNT}.
133      * @param mailboxId ID of the mailbox to search, or {@link Mailbox#NO_MAILBOX} to perform
134      *     global search.
135      * @param query query string.
136      */
createSearchIntent(Activity fromActivity, long accountId, long mailboxId, String query)137     public static Intent createSearchIntent(Activity fromActivity, long accountId,
138             long mailboxId, String query) {
139         Preconditions.checkArgument(Account.isNormalAccount(accountId),
140                 "Can only search in normal accounts");
141 
142         // Note that a search doesn't use a restart intent, as we want another instance of
143         // the activity to sit on the stack for search.
144         Intent i = new Intent(fromActivity, EmailActivity.class);
145         i.putExtra(EXTRA_ACCOUNT_ID, accountId);
146         i.putExtra(EXTRA_MAILBOX_ID, mailboxId);
147         i.putExtra(EXTRA_QUERY_STRING, query);
148         i.setAction(Intent.ACTION_SEARCH);
149         return i;
150     }
151 
152     /**
153      * Initialize {@link #mUIController}.
154      */
initUIController()155     private void initUIController() {
156         mUIController = UiUtilities.useTwoPane(this)
157                 ? new UIControllerTwoPane(this) : new UIControllerOnePane(this);
158     }
159 
160     @Override
onCreate(Bundle savedInstanceState)161     protected void onCreate(Bundle savedInstanceState) {
162         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onCreate");
163 
164         // UIController is used in onPrepareOptionsMenu(), which can be called from within
165         // super.onCreate(), so we need to initialize it here.
166         initUIController();
167 
168         super.onCreate(savedInstanceState);
169         ActivityHelper.debugSetWindowFlags(this);
170         setContentView(mUIController.getLayoutId());
171 
172         mUIController.onActivityViewReady();
173 
174         mController = Controller.getInstance(this);
175         mControllerResult = new ControllerResultUiThreadWrapper<ControllerResult>(new Handler(),
176                 new ControllerResult());
177         mController.addResultCallback(mControllerResult);
178 
179         // Set up views
180         // TODO Probably better to extract mErrorMessageView related code into a separate class,
181         // so that it'll be easy to reuse for the phone activities.
182         TextView errorMessage = (TextView) findViewById(R.id.error_message);
183         errorMessage.setOnClickListener(this);
184         int errorBannerHeight = getResources().getDimensionPixelSize(R.dimen.error_message_height);
185         mErrorBanner = new BannerController(this, errorMessage, errorBannerHeight);
186 
187         if (savedInstanceState != null) {
188             mUIController.onRestoreInstanceState(savedInstanceState);
189         } else {
190             initFromIntent();
191         }
192         mUIController.onActivityCreated();
193     }
194 
initFromIntent()195     private void initFromIntent() {
196         final Intent intent = getIntent();
197         final MessageListContext viewContext = MessageListContext.forIntent(this, intent);
198         final long messageId = intent.getLongExtra(EXTRA_MESSAGE_ID, Message.NO_MESSAGE);
199 
200         mUIController.open(viewContext, messageId);
201     }
202 
203     @Override
onSaveInstanceState(Bundle outState)204     protected void onSaveInstanceState(Bundle outState) {
205         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
206             Log.d(Logging.LOG_TAG, this + " onSaveInstanceState");
207         }
208         super.onSaveInstanceState(outState);
209         mUIController.onSaveInstanceState(outState);
210     }
211 
212     // FragmentInstallable
213     @Override
onInstallFragment(Fragment fragment)214     public void onInstallFragment(Fragment fragment) {
215         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
216             Log.d(Logging.LOG_TAG, this + " onInstallFragment fragment=" + fragment);
217         }
218         mUIController.onInstallFragment(fragment);
219     }
220 
221     // FragmentInstallable
222     @Override
onUninstallFragment(Fragment fragment)223     public void onUninstallFragment(Fragment fragment) {
224         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
225             Log.d(Logging.LOG_TAG, this + " onUninstallFragment fragment=" + fragment);
226         }
227         mUIController.onUninstallFragment(fragment);
228     }
229 
230     @Override
onStart()231     protected void onStart() {
232         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onStart");
233         super.onStart();
234         mUIController.onActivityStart();
235     }
236 
237     @Override
onResume()238     protected void onResume() {
239         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onResume");
240         super.onResume();
241         mUIController.onActivityResume();
242         /**
243          * In {@link MessageList#onResume()}, we go back to {@link Welcome} if an account
244          * has been added/removed. We don't need to do that here, because we fetch the most
245          * up-to-date account list. Additionally, we detect and do the right thing if all
246          * of the accounts have been removed.
247          */
248     }
249 
250     @Override
onPause()251     protected void onPause() {
252         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onPause");
253         super.onPause();
254         mUIController.onActivityPause();
255     }
256 
257     @Override
onStop()258     protected void onStop() {
259         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onStop");
260         super.onStop();
261         mUIController.onActivityStop();
262     }
263 
264     @Override
onDestroy()265     protected void onDestroy() {
266         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) Log.d(Logging.LOG_TAG, this + " onDestroy");
267         mController.removeResultCallback(mControllerResult);
268         mTaskTracker.cancellAllInterrupt();
269         mUIController.onActivityDestroy();
270         super.onDestroy();
271     }
272 
273     @Override
onBackPressed()274     public void onBackPressed() {
275         if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) {
276             Log.d(Logging.LOG_TAG, this + " onBackPressed");
277         }
278         if (!mUIController.onBackPressed(true)) {
279             // Not handled by UIController -- perform the default. i.e. close the app.
280             super.onBackPressed();
281         }
282     }
283 
284     @Override
onClick(View v)285     public void onClick(View v) {
286         switch (v.getId()) {
287             case R.id.error_message:
288                 dismissErrorMessage();
289                 break;
290         }
291     }
292 
293     /**
294      * Force dismiss the error banner.
295      */
dismissErrorMessage()296     private void dismissErrorMessage() {
297         mErrorBanner.dismiss();
298     }
299 
300     @Override
onCreateOptionsMenu(Menu menu)301     public boolean onCreateOptionsMenu(Menu menu) {
302         return mUIController.onCreateOptionsMenu(getMenuInflater(), menu);
303     }
304 
305     @Override
onPrepareOptionsMenu(Menu menu)306     public boolean onPrepareOptionsMenu(Menu menu) {
307         return mUIController.onPrepareOptionsMenu(getMenuInflater(), menu);
308     }
309 
310     /**
311      * Called when the search key is pressd.
312      *
313      * Use the below command to emulate the key press on devices without the search key.
314      * adb shell input keyevent 84
315      */
316     @Override
onSearchRequested()317     public boolean onSearchRequested() {
318         if (Email.DEBUG) {
319             Log.d(Logging.LOG_TAG, this + " onSearchRequested");
320         }
321         mUIController.onSearchRequested();
322         return true; // Event handled.
323     }
324 
325     @Override
326     @SuppressWarnings("deprecation")
onOptionsItemSelected(MenuItem item)327     public boolean onOptionsItemSelected(MenuItem item) {
328         if (mUIController.onOptionsItemSelected(item)) {
329             return true;
330         }
331         return super.onOptionsItemSelected(item);
332     }
333 
334     /**
335      * A {@link Controller.Result} to detect connection status.
336      */
337     private class ControllerResult extends Controller.Result {
338         @Override
sendMailCallback( MessagingException result, long accountId, long messageId, int progress)339         public void sendMailCallback(
340                 MessagingException result, long accountId, long messageId, int progress) {
341             handleError(result, accountId, progress);
342         }
343 
344         @Override
serviceCheckMailCallback( MessagingException result, long accountId, long mailboxId, int progress, long tag)345         public void serviceCheckMailCallback(
346                 MessagingException result, long accountId, long mailboxId, int progress, long tag) {
347             handleError(result, accountId, progress);
348         }
349 
350         @Override
updateMailboxCallback(MessagingException result, long accountId, long mailboxId, int progress, int numNewMessages, ArrayList<Long> addedMessages)351         public void updateMailboxCallback(MessagingException result, long accountId, long mailboxId,
352                 int progress, int numNewMessages, ArrayList<Long> addedMessages) {
353             handleError(result, accountId, progress);
354         }
355 
356         @Override
updateMailboxListCallback( MessagingException result, long accountId, int progress)357         public void updateMailboxListCallback(
358                 MessagingException result, long accountId, int progress) {
359             handleError(result, accountId, progress);
360         }
361 
362         @Override
loadAttachmentCallback(MessagingException result, long accountId, long messageId, long attachmentId, int progress)363         public void loadAttachmentCallback(MessagingException result, long accountId,
364                 long messageId, long attachmentId, int progress) {
365             handleError(result, accountId, progress);
366         }
367 
368         @Override
loadMessageForViewCallback(MessagingException result, long accountId, long messageId, int progress)369         public void loadMessageForViewCallback(MessagingException result, long accountId,
370                 long messageId, int progress) {
371             handleError(result, accountId, progress);
372         }
373 
handleError(final MessagingException result, final long accountId, int progress)374         private void handleError(final MessagingException result, final long accountId,
375                 int progress) {
376             if (accountId == -1) {
377                 return;
378             }
379             if (result == null) {
380                 if (progress > 0) {
381                     // Connection now working; clear the error message banner
382                     if (mLastErrorAccountId == accountId) {
383                         dismissErrorMessage();
384                     }
385                 }
386             } else {
387                 Account account = Account.restoreAccountWithId(EmailActivity.this, accountId);
388                 if (account == null) return;
389                 String message =
390                     MessagingExceptionStrings.getErrorString(EmailActivity.this, result);
391                 if (!TextUtils.isEmpty(account.mDisplayName)) {
392                     // TODO Use properly designed layout. Don't just concatenate strings;
393                     // which is generally poor for I18N.
394                     message = message + "   (" + account.mDisplayName + ")";
395                 }
396                 if (mErrorBanner.show(message)) {
397                     mLastErrorAccountId = accountId;
398                 }
399              }
400         }
401     }
402 }
403