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