Home | History | Annotate | Download | only in ui
      1 /*******************************************************************************
      2  *      Copyright (C) 2012 Google Inc.
      3  *      Licensed to The Android Open Source Project.
      4  *
      5  *      Licensed under the Apache License, Version 2.0 (the "License");
      6  *      you may not use this file except in compliance with the License.
      7  *      You may obtain a copy of the License at
      8  *
      9  *           http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *      Unless required by applicable law or agreed to in writing, software
     12  *      distributed under the License is distributed on an "AS IS" BASIS,
     13  *      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *      See the License for the specific language governing permissions and
     15  *      limitations under the License.
     16  *******************************************************************************/
     17 
     18 package com.android.mail.ui;
     19 
     20 import android.app.Dialog;
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.res.Configuration;
     25 import android.nfc.NdefMessage;
     26 import android.nfc.NdefRecord;
     27 import android.nfc.NfcAdapter;
     28 import android.nfc.NfcEvent;
     29 import android.os.Bundle;
     30 import android.support.v7.app.ActionBar;
     31 import android.support.v7.view.ActionMode;
     32 import android.support.v7.widget.Toolbar;
     33 import android.view.KeyEvent;
     34 import android.view.Menu;
     35 import android.view.MenuItem;
     36 import android.view.MotionEvent;
     37 import android.view.accessibility.AccessibilityManager;
     38 
     39 import com.android.bitmap.BitmapCache;
     40 import com.android.bitmap.UnrefedBitmapCache;
     41 import com.android.mail.R;
     42 import com.android.mail.analytics.Analytics;
     43 import com.android.mail.analytics.AnalyticsTimer;
     44 import com.android.mail.bitmap.ContactResolver;
     45 import com.android.mail.compose.ComposeActivity;
     46 import com.android.mail.providers.Account;
     47 import com.android.mail.providers.Folder;
     48 import com.android.mail.providers.SearchRecentSuggestionsProvider;
     49 import com.android.mail.providers.SuggestionsProvider;
     50 import com.android.mail.utils.StorageLowState;
     51 import com.android.mail.utils.Utils;
     52 import com.android.mail.utils.ViewUtils;
     53 
     54 import java.io.UnsupportedEncodingException;
     55 import java.net.URLEncoder;
     56 
     57 /**
     58  * This is the root activity container that holds the left navigation fragment
     59  * (usually a list of folders), and the main content fragment (either a
     60  * conversation list or a conversation view).
     61  */
     62 public class MailActivity extends AbstractMailActivity implements ControllableActivity {
     63 
     64     /** 339KB cache fits 10 bitmaps at 33856 bytes each. */
     65     private static final int SENDERS_IMAGES_CACHE_TARGET_SIZE_BYTES = 1024 * 339;
     66     private static final float SENDERS_IMAGES_PREVIEWS_CACHE_NON_POOLED_FRACTION = 0f;
     67     /** Each string has upper estimate of 50 bytes, so this cache would be 5KB. */
     68     private static final int SENDERS_IMAGES_PREVIEWS_CACHE_NULL_CAPACITY = 100;
     69 
     70     /**
     71      * The activity controller to which we delegate most Activity lifecycle events.
     72      */
     73     private ActivityController mController;
     74 
     75     private ViewMode mViewMode;
     76 
     77     private ToastBarOperation mPendingToastOp;
     78     private boolean mAccessibilityEnabled;
     79     private AccessibilityManager mAccessibilityManager;
     80 
     81     protected ConversationListHelper mConversationListHelper;
     82 
     83     /**
     84      * The account name currently in use. Used to construct the NFC mailto: message. This needs
     85      * to be static since the {@link ComposeActivity} needs to statically change the account name
     86      * and have the NFC message changed accordingly.
     87      */
     88     protected static String sAccountName = null;
     89 
     90     private BitmapCache mSendersImageCache;
     91 
     92     private CustomViewToolbar mCustomViewToolbar;
     93 
     94     /**
     95      * Create an NFC message (in the NDEF: Nfc Data Exchange Format) to instruct the recepient to
     96      * send an email to the current account.
     97      */
     98     private static class NdefMessageMaker implements NfcAdapter.CreateNdefMessageCallback {
     99         @Override
    100         public NdefMessage createNdefMessage(NfcEvent event) {
    101             if (sAccountName == null) {
    102                 return null;
    103             }
    104             return getMailtoNdef(sAccountName);
    105         }
    106 
    107         /**
    108          * Returns an NDEF message with a single mailto URI record
    109          * for the given email address.
    110          */
    111         private static NdefMessage getMailtoNdef(String account) {
    112             byte[] accountBytes;
    113             try {
    114                 accountBytes = URLEncoder.encode(account, "UTF-8").getBytes("UTF-8");
    115             } catch (UnsupportedEncodingException e) {
    116                 accountBytes = account.getBytes();
    117             }
    118             byte prefix = 0x06; // mailto:
    119             byte[] recordBytes = new byte[accountBytes.length + 1];
    120             recordBytes[0] = prefix;
    121             System.arraycopy(accountBytes, 0, recordBytes, 1, accountBytes.length);
    122             NdefRecord mailto = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI,
    123                     new byte[0], recordBytes);
    124             return new NdefMessage(new NdefRecord[] { mailto });
    125         }
    126     }
    127 
    128     private final NdefMessageMaker mNdefHandler = new NdefMessageMaker();
    129 
    130     public MailActivity() {
    131         super();
    132         mConversationListHelper = new ConversationListHelper();
    133     }
    134 
    135     @Override
    136     public boolean dispatchTouchEvent(MotionEvent ev) {
    137         mController.onTouchEvent(ev);
    138         return super.dispatchTouchEvent(ev);
    139     }
    140 
    141     /**
    142      * Default implementation returns a null view mode.
    143      */
    144     @Override
    145     public ViewMode getViewMode() {
    146         return mViewMode;
    147     }
    148 
    149     @Override
    150     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    151         mController.onActivityResult(requestCode, resultCode, data);
    152     }
    153 
    154     @Override
    155     public void onBackPressed() {
    156         if (!mController.onBackPressed()) {
    157             super.onBackPressed();
    158         }
    159     }
    160 
    161     @Override
    162     public void onCreate(Bundle savedState) {
    163         super.onCreate(savedState);
    164         // Log the start time if this is launched from the launcher with no saved states
    165         Intent i = getIntent();
    166         if (i != null && i.getCategories() != null &&
    167                 i.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
    168             AnalyticsTimer.getInstance().trackStart(AnalyticsTimer.COLD_START_LAUNCHER);
    169         }
    170 
    171         resetSenderImageCache();
    172         mViewMode = new ViewMode();
    173         final boolean tabletUi = Utils.useTabletUI(this.getResources());
    174         mController = ControllerFactory.forActivity(this, mViewMode, tabletUi);
    175 
    176         setContentView(mController.getContentViewResource());
    177 
    178         final Toolbar toolbar = (Toolbar) findViewById(R.id.mail_toolbar);
    179         if (toolbar instanceof CustomViewToolbar) {
    180             // Tablets use CustomViewToolbar to override the default search menu item positioning.
    181             mCustomViewToolbar = (CustomViewToolbar) toolbar;
    182             mCustomViewToolbar.setController(this, mController, mViewMode);
    183             mController.addConversationListLayoutListener(mCustomViewToolbar);
    184         }
    185 
    186         setSupportActionBar(toolbar);
    187         toolbar.setNavigationOnClickListener(mController.getNavigationViewClickListener());
    188 
    189         final ActionBar actionBar = getSupportActionBar();
    190         if (actionBar != null) {
    191             // Hide the app icon.
    192             actionBar.setIcon(android.R.color.transparent);
    193             actionBar.setDisplayUseLogoEnabled(false);
    194         }
    195 
    196         // Must be done after setting up action bar
    197         mController.onCreate(savedState);
    198 
    199         mAccessibilityManager =
    200                 (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
    201         mAccessibilityEnabled = mAccessibilityManager.isEnabled();
    202         final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    203         if (nfcAdapter != null) {
    204             nfcAdapter.setNdefPushMessageCallback(mNdefHandler, this);
    205         }
    206 
    207         // Detect presence of hardware keyboard and log it on Analytics
    208         final int hardKeyboardHidden = getResources().getConfiguration().hardKeyboardHidden;
    209         if (hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
    210             Analytics.getInstance().sendEvent("configuration", "keyboard", "use_hardware_keyboard",
    211                     0);
    212         } else {
    213             Analytics.getInstance().sendEvent("configuration", "keyboard",
    214                     "do_not_use_hardware_keyboard", 0);
    215         }
    216     }
    217 
    218     @Override
    219     protected void onPostCreate(Bundle savedInstanceState) {
    220         super.onPostCreate(savedInstanceState);
    221 
    222         mController.onPostCreate(savedInstanceState);
    223     }
    224 
    225     @Override
    226     public void onConfigurationChanged(Configuration newConfig) {
    227         super.onConfigurationChanged(newConfig);
    228         mController.onConfigurationChanged(newConfig);
    229     }
    230 
    231     @Override
    232     protected void onRestart() {
    233         super.onRestart();
    234         mController.onRestart();
    235     }
    236 
    237     /**
    238      * Constructs and sets the default NFC message. This message instructs the receiver to send
    239      * email to the account provided as the argument. This message is to be shared with
    240      * "zero-clicks" using NFC. The message will be available as long as the current activity is in
    241      * the foreground.
    242      *
    243      * @param account The email address to send mail to.
    244      */
    245     public static void setNfcMessage(String account) {
    246         sAccountName = account;
    247     }
    248 
    249     @Override
    250     protected void onRestoreInstanceState(Bundle savedInstanceState) {
    251         super.onRestoreInstanceState(savedInstanceState);
    252         mController.onRestoreInstanceState(savedInstanceState);
    253     }
    254 
    255     @Override
    256     public Dialog onCreateDialog(int id, Bundle bundle) {
    257         final Dialog dialog = mController.onCreateDialog(id, bundle);
    258         return dialog == null ? super.onCreateDialog(id, bundle) : dialog;
    259     }
    260 
    261     @Override
    262     public boolean onCreateOptionsMenu(Menu menu) {
    263         return mController.onCreateOptionsMenu(menu) || super.onCreateOptionsMenu(menu);
    264     }
    265 
    266     @Override
    267     public boolean onKeyDown(int keyCode, KeyEvent event) {
    268         return mController.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event);
    269     }
    270 
    271     @Override
    272     public boolean onOptionsItemSelected(MenuItem item) {
    273         return mController.onOptionsItemSelected(item) || super.onOptionsItemSelected(item);
    274     }
    275 
    276     @Override
    277     public void onPause() {
    278         super.onPause();
    279         mController.onPause();
    280     }
    281 
    282     @Override
    283     public void onPrepareDialog(int id, Dialog dialog, Bundle bundle) {
    284         super.onPrepareDialog(id, dialog, bundle);
    285         mController.onPrepareDialog(id, dialog, bundle);
    286     }
    287 
    288     @Override
    289     public boolean onPrepareOptionsMenu(Menu menu) {
    290         mController.onPrepareOptionsMenu(menu);
    291         return super.onPrepareOptionsMenu(menu);
    292     }
    293 
    294     @Override
    295     public void onResume() {
    296         super.onResume();
    297         mController.onResume();
    298         final boolean enabled = mAccessibilityManager.isEnabled();
    299         if (enabled != mAccessibilityEnabled) {
    300             onAccessibilityStateChanged(enabled);
    301         }
    302         // App has resumed, re-check the top-level storage situation.
    303         StorageLowState.checkStorageLowMode(this);
    304     }
    305 
    306     @Override
    307     public void onSaveInstanceState(Bundle outState) {
    308         super.onSaveInstanceState(outState);
    309         mController.onSaveInstanceState(outState);
    310     }
    311 
    312     @Override
    313     protected void onStart() {
    314         super.onStart();
    315         mController.onStart();
    316     }
    317 
    318     @Override
    319     public boolean onSearchRequested() {
    320         mController.startSearch();
    321         return true;
    322     }
    323 
    324     @Override
    325     protected void onStop() {
    326         super.onStop();
    327         mController.onStop();
    328     }
    329 
    330     @Override
    331     protected void onDestroy() {
    332         super.onDestroy();
    333         mController.onDestroy();
    334 
    335         if (mCustomViewToolbar != null) {
    336             mCustomViewToolbar.onDestroy();
    337         }
    338     }
    339 
    340     @Override
    341     public void onWindowFocusChanged(boolean hasFocus) {
    342         super.onWindowFocusChanged(hasFocus);
    343         mController.onWindowFocusChanged(hasFocus);
    344     }
    345 
    346     @Override
    347     public String toString() {
    348         final StringBuilder sb = new StringBuilder(super.toString());
    349         sb.append("{ViewMode=");
    350         sb.append(mViewMode);
    351         sb.append(" controller=");
    352         sb.append(mController);
    353         sb.append(" current_focus=");
    354         sb.append(getCurrentFocus());
    355         sb.append("}");
    356         return sb.toString();
    357     }
    358 
    359     @Override
    360     public ConversationListCallbacks getListHandler() {
    361         return mController;
    362     }
    363 
    364     @Override
    365     public FolderSelector getFolderSelector() {
    366         return mController;
    367     }
    368 
    369     @Override
    370     public FolderController getFolderController() {
    371         return mController;
    372     }
    373 
    374     @Override
    375     public ConversationCheckedSet getCheckedSet() {
    376         return mController.getCheckedSet();
    377     }
    378 
    379     @Override
    380     public void onUndoAvailable(ToastBarOperation undoOp) {
    381         mController.onUndoAvailable(undoOp);
    382     }
    383 
    384     @Override
    385     public Folder getHierarchyFolder() {
    386         return mController.getHierarchyFolder();
    387     }
    388 
    389     @Override
    390     public ConversationUpdater getConversationUpdater() {
    391         return mController;
    392     }
    393 
    394     @Override
    395     public ErrorListener getErrorListener() {
    396         return mController;
    397     }
    398 
    399     @Override
    400     public void setPendingToastOperation(ToastBarOperation op) {
    401         mPendingToastOp = op;
    402     }
    403 
    404     @Override
    405     public ToastBarOperation getPendingToastOperation() {
    406         return mPendingToastOp;
    407     }
    408 
    409     @Override
    410     public void onAnimationEnd(AnimatedAdapter animatedAdapter) {
    411         mController.onAnimationEnd(animatedAdapter);
    412     }
    413 
    414     @Override
    415     public AccountController getAccountController() {
    416         return mController;
    417     }
    418 
    419     @Override
    420     public RecentFolderController getRecentFolderController() {
    421         return mController;
    422     }
    423 
    424     @Override
    425     public DrawerController getDrawerController() {
    426         return mController.getDrawerController();
    427     }
    428 
    429     @Override
    430     public KeyboardNavigationController getKeyboardNavigationController() {
    431         return mController;
    432     }
    433 
    434     @Override
    435     public void onFooterViewLoadMoreClick(Folder folder) {
    436         mController.onFooterViewLoadMoreClick(folder);
    437     }
    438 
    439     @Override
    440     public boolean isAccessibilityEnabled() {
    441         return mAccessibilityEnabled;
    442     }
    443 
    444     public void onAccessibilityStateChanged(boolean enabled) {
    445         mAccessibilityEnabled = enabled;
    446         mController.onAccessibilityStateChanged();
    447     }
    448 
    449     @Override
    450     public final ConversationListHelper getConversationListHelper() {
    451         return mConversationListHelper;
    452     }
    453 
    454     @Override
    455     public FragmentLauncher getFragmentLauncher() {
    456         return mController;
    457     }
    458 
    459     @Override
    460     public ContactLoaderCallbacks getContactLoaderCallbacks() {
    461         return new ContactLoaderCallbacks(getActivityContext());
    462     }
    463 
    464     @Override
    465     public ContactResolver getContactResolver(ContentResolver resolver, BitmapCache bitmapCache) {
    466         return new ContactResolver(resolver, bitmapCache);
    467     }
    468 
    469     @Override
    470     public BitmapCache getSenderImageCache() {
    471         return mSendersImageCache;
    472     }
    473 
    474     @Override
    475     public void resetSenderImageCache() {
    476         mSendersImageCache = createNewSenderImageCache();
    477     }
    478 
    479     private BitmapCache createNewSenderImageCache() {
    480         return new UnrefedBitmapCache(Utils.isLowRamDevice(this) ?
    481                 0 : SENDERS_IMAGES_CACHE_TARGET_SIZE_BYTES,
    482                 SENDERS_IMAGES_PREVIEWS_CACHE_NON_POOLED_FRACTION,
    483                 SENDERS_IMAGES_PREVIEWS_CACHE_NULL_CAPACITY);
    484     }
    485 
    486     @Override
    487     public void showHelp(Account account, int viewMode) {
    488         int helpContext = ViewMode.isConversationMode(viewMode)
    489                 ? R.string.conversation_view_help_context
    490                 : R.string.conversation_list_help_context;
    491         Utils.showHelp(this, account, getString(helpContext));
    492     }
    493 
    494     public SearchRecentSuggestionsProvider getSuggestionsProvider() {
    495         return new SuggestionsProvider(this);
    496     }
    497 
    498     @Override
    499     public void onSupportActionModeStarted(ActionMode mode) {
    500         super.onSupportActionModeStarted(mode);
    501         ViewUtils.setStatusBarColor(this, R.color.action_mode_statusbar_color);
    502     }
    503 
    504     @Override
    505     public void onSupportActionModeFinished(ActionMode mode) {
    506         super.onSupportActionModeFinished(mode);
    507         ViewUtils.setStatusBarColor(this, R.color.mail_activity_status_bar_color);
    508     }
    509 }
    510