Merge "Use Drawables for senders image. Less animation jank, less GC jank, less memory." into jb-ub-mail-ur11
diff --git a/res/raw/template_conversation_upper.html b/res/raw/template_conversation_upper.html
index 5ba3a84..fb69324 100644
--- a/res/raw/template_conversation_upper.html
+++ b/res/raw/template_conversation_upper.html
@@ -55,6 +55,7 @@
             -webkit-transform: translate3d(0, 0, 0);
         }
     }
+    %s
   </style>
 </head>
 <body style="margin: 0 %spx;">
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 955febf..40a965e 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -32,7 +32,7 @@
   <string-array name="compose_modes">
     <item msgid="8631190144210933782">"जवाब दें"</item>
     <item msgid="4085029907540221568">"सभी को जवाब दें"</item>
-    <item msgid="857480048798815437">"अग्रेषित करें"</item>
+    <item msgid="857480048798815437">"इसको भेजें"</item>
   </string-array>
     <string name="reply_attribution" msgid="717207316577386788">"<xliff:g id="DATE">%s</xliff:g> को, <xliff:g id="PERSON">%s</xliff:g> ने लिखा:"</string>
     <string name="forward_attribution" msgid="1498936339458535158">"---------- अग्रेषित संदेश ----------&lt;br&gt;प्रेषक: <xliff:g id="FROM">%1$s</xliff:g>&lt;br&gt;दिनांक: <xliff:g id="DATE">%2$s</xliff:g>&lt;br&gt;विषय: <xliff:g id="SUBJECT">%3$s</xliff:g>&lt;br&gt;प्रति: <xliff:g id="TO">%4$s</xliff:g>&lt;br&gt;"</string>
@@ -69,7 +69,7 @@
     <string name="reply" msgid="2032052598496088561">"प्रत्‍युत्तर दें"</string>
     <string name="reply_all" msgid="7257248094625487557">"सब को जवाब दें"</string>
     <string name="resume_draft" msgid="6450150180834092146">"संपादित करें"</string>
-    <string name="forward" msgid="6822914459902983767">"अग्रेषित करें"</string>
+    <string name="forward" msgid="6822914459902983767">"इसको भेजें"</string>
     <string name="menu_compose" msgid="6274193058224230645">"लिखें"</string>
     <string name="menu_change_folders" msgid="1542713666608888717">"फ़ोल्डर बदलें"</string>
     <string name="menu_move_to" msgid="9138296669516358542">"इसमें ले जाएं"</string>
@@ -106,7 +106,7 @@
     <string name="download_again" msgid="7418568915429156518">"पुनः डाउनलोड करें"</string>
     <string name="more_info_attachment" msgid="717244337981816703">"जानकारी"</string>
     <string name="attachment_type_blocked" msgid="7878184764428642891">"दुर्भाग्‍यवश, आप इस प्रकार के अनुलग्‍नक को सहेज या खोल नहीं सकते."</string>
-    <string name="no_application_found" msgid="1945184804521392093">"कोई भी एप्लिकेशन इस अनुलग्नक को देखने के लिए नहीं खोल सकता."</string>
+    <string name="no_application_found" msgid="1945184804521392093">"कोई भी एप्स इस अनुलग्नक को देखने के लिए नहीं खोल सकता."</string>
     <string name="fetching_attachment" msgid="2599763030649526179">"अनुलग्‍नक फ़ेच कर रहा है"</string>
     <string name="please_wait" msgid="8797634225088247121">"कृपया प्रतीक्षा करें…"</string>
     <string name="saved" msgid="6660844285730790469">"सहेजा गया, <xliff:g id="SIZE">%s</xliff:g>"</string>
@@ -393,7 +393,7 @@
     <string name="account_settings_param" msgid="5277032997773498476">"खाता सेटिंग"</string>
     <string name="unsent_messages_in_outbox" msgid="8754987959475577698">"<xliff:g id="OUTBOX">%2$s</xliff:g> में <xliff:g id="NUMBER">%1$s</xliff:g> अप्रेषित"</string>
     <string name="turn_auto_sync_on_dialog_title" msgid="8981949974921704813">"स्वत:-समन्वयन चालू करें?"</string>
-    <string name="turn_auto_sync_on_dialog_body" msgid="5070811094706347971">"आपके द्वारा न केवल Gmail में, बल्कि सभी एप्लिकेशन और खातों में किए जाने वाले बदलाव, वेब, आपके अन्य उपकरणों, और आपके <xliff:g id="PHONE_OR_TABLET">%1$s</xliff:g> के बीच समन्वयित किए जाएंगे."</string>
+    <string name="turn_auto_sync_on_dialog_body" msgid="5070811094706347971">"आपके द्वारा न केवल Gmail में, बल्कि सभी एप्स और खातों में किए जाने वाले बदलाव, वेब, आपके अन्य उपकरणों, और आपके <xliff:g id="PHONE_OR_TABLET">%1$s</xliff:g> के बीच समन्वयित किए जाएंगे."</string>
     <string name="phone" msgid="5171715391553213328">"फ़ोन"</string>
     <string name="tablet" msgid="7956969657216748522">"टेबलेट"</string>
     <string name="turn_auto_sync_on_dialog_confirm_btn" msgid="1445158420197688714">"चालू करें"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d4106d6..5453934 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -935,4 +935,8 @@
          LIMIT=30] -->
     <string name="turn_auto_sync_on_dialog_confirm_btn">Turn on</string>
 
+    <!-- Button in conversation list to show more folders [CHAR LIMIT=50] -->
+    <string name="show_n_more_folders">Show <xliff:g id="number">%1$s</xliff:g> more folders</string>
+    <!-- Button in conversation list to hide folders [CHAR LIMIT=50] -->
+    <string name="hide_folders">Hide folders</string>
 </resources>
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index 6df2d18..7a3ca0f 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -165,6 +165,8 @@
 
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
 
+    private final boolean mCachingEnabled;
+
     private void setCursor(UnderlyingCursorWrapper cursor) {
         // If we have an existing underlying cursor, make sure it's closed
         if (mUnderlyingCursor != null) {
@@ -193,6 +195,9 @@
         mName = name;
         qProjection = UIProvider.CONVERSATION_PROJECTION;
         mCursorObserver = new CursorObserver(new Handler(Looper.getMainLooper()));
+
+        // Disable caching on low memory devices
+        mCachingEnabled = !Utils.isLowRamDevice(activity);
     }
 
     /**
@@ -357,7 +362,7 @@
          * notes on thread safety.
          */
         private int mCachePos;
-        private boolean mCachingEnabled = true;
+        private boolean mCachingEnabled;
         private final NewCursorUpdateObserver mCursorUpdateObserver;
         private boolean mUpdateObserverRegistered = false;
 
@@ -370,9 +375,11 @@
 
         private boolean mCursorUpdated = false;
 
-        public UnderlyingCursorWrapper(Cursor result) {
+        public UnderlyingCursorWrapper(Cursor result, boolean cachingEnabled) {
             super(result);
 
+            mCachingEnabled = cachingEnabled;
+
             // Register the content observer immediately, as we want to make sure that we don't miss
             // any updates
             mCursorUpdateObserver =
@@ -639,7 +646,8 @@
                     uri, time, result.getCount());
         }
         System.gc();
-        return new UnderlyingCursorWrapper(result);
+
+        return new UnderlyingCursorWrapper(result, mCachingEnabled);
     }
 
     static boolean offUiThread() {
diff --git a/src/com/android/mail/preferences/MailPrefs.java b/src/com/android/mail/preferences/MailPrefs.java
index c6146c8..3791055 100644
--- a/src/com/android/mail/preferences/MailPrefs.java
+++ b/src/com/android/mail/preferences/MailPrefs.java
@@ -23,6 +23,7 @@
 import com.android.mail.providers.Account;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
 import com.android.mail.widget.BaseWidgetProvider;
 
 import com.google.common.collect.ImmutableSet;
@@ -396,6 +397,11 @@
     }
 
     public boolean getShowSenderImages() {
+        if (Utils.isLowRamDevice(getContext())) {
+            // Do not show sender images in conversation list on low memory devices since they are
+            // expensive to render.
+            return false;
+        }
         final SharedPreferences sharedPreferences = getSharedPreferences();
         return sharedPreferences.getBoolean(PreferenceKeys.SHOW_SENDER_IMAGES, true);
     }
@@ -406,6 +412,9 @@
     }
 
     public boolean getShowAttachmentPreviews() {
+        if (Utils.isLowRamDevice(getContext())) {
+            return false;
+        }
         final SharedPreferences sharedPreferences = getSharedPreferences();
         return sharedPreferences.getBoolean(PreferenceKeys.SHOW_ATTACHMENT_PREVIEWS, true);
     }
diff --git a/src/com/android/mail/ui/HtmlConversationTemplates.java b/src/com/android/mail/ui/HtmlConversationTemplates.java
index a285247..d368454 100644
--- a/src/com/android/mail/ui/HtmlConversationTemplates.java
+++ b/src/com/android/mail/ui/HtmlConversationTemplates.java
@@ -23,6 +23,7 @@
 import com.android.mail.R;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.Utils;
 import com.google.common.annotations.VisibleForTesting;
 
 import java.io.IOException;
@@ -181,7 +182,9 @@
         }
 
         reset();
-        append(sConversationUpper, sideMargin, conversationHeaderHeight);
+        final String border = Utils.isRunningKitkatOrLater() ?
+                "img[blocked-src] { border: 1px solid #CCCCCC; }" : "";
+        append(sConversationUpper, border,  sideMargin, conversationHeaderHeight);
         mInProgress = true;
     }
 
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index f3fd875..c4927cd 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -22,6 +22,7 @@
 import com.google.android.mail.common.html.parser.HtmlTreeBuilder;
 import com.google.common.collect.Maps;
 
+import android.app.ActivityManager;
 import android.app.Fragment;
 import android.app.SearchManager;
 import android.content.Context;
@@ -140,6 +141,26 @@
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
     }
 
+    public static boolean isRunningKitkatOrLater() {
+        // TODO - change to SDK_INT check when API is finalized
+        return Build.VERSION.CODENAME.startsWith("K");
+    }
+
+    /**
+     * @return Whether we are running on a low memory device.  This is used to disable certain
+     * memory intensive features in the app.
+     */
+    public static boolean isLowRamDevice(Context context) {
+        // TODO: use SDK_INT to check if device is KitKat or greater.
+        if (Build.VERSION.CODENAME.startsWith("K")) {
+            final ActivityManager am = (ActivityManager) context.getSystemService(
+                    Context.ACTIVITY_SERVICE);
+            return am.isLowRamDevice();
+        } else {
+            return false;
+        }
+    }
+
     /**
      * Sets WebView in a restricted mode suitable for email use.
      *
diff --git a/src/com/android/mail/widget/BaseWidgetProvider.java b/src/com/android/mail/widget/BaseWidgetProvider.java
index 8d0be11..98a5ad6 100644
--- a/src/com/android/mail/widget/BaseWidgetProvider.java
+++ b/src/com/android/mail/widget/BaseWidgetProvider.java
@@ -52,7 +52,6 @@
     public static final String EXTRA_FOLDER_URI = "folder-uri";
     public static final String EXTRA_FOLDER_CONVERSATION_LIST_URI = "folder-conversation-list-uri";
     public static final String EXTRA_FOLDER_DISPLAY_NAME = "folder-display-name";
-    public static final String EXTRA_UNREAD = "unread";
     public static final String EXTRA_UPDATE_ALL_WIDGETS = "update-all-widgets";
     public static final String WIDGET_ACCOUNT_PREFIX = "widget-account-";
 
@@ -139,8 +138,8 @@
         } else if (Utils.ACTION_NOTIFY_DATASET_CHANGED.equals(action)) {
             // Receive notification for a certain account.
             final Bundle extras = intent.getExtras();
-            final Uri accountUri = (Uri)extras.getParcelable(Utils.EXTRA_ACCOUNT_URI);
-            final Uri folderUri = (Uri)extras.getParcelable(Utils.EXTRA_FOLDER_URI);
+            final Uri accountUri = extras.getParcelable(Utils.EXTRA_ACCOUNT_URI);
+            final Uri folderUri = extras.getParcelable(Utils.EXTRA_FOLDER_URI);
             final boolean updateAllWidgets = extras.getBoolean(EXTRA_UPDATE_ALL_WIDGETS, false);
 
             if (accountUri == null && Utils.isEmpty(folderUri) && !updateAllWidgets) {
@@ -383,12 +382,12 @@
                 WidgetService.class);
     }
 
-    private final void migrateAllLegacyWidgetInformation(Context context) {
+    private void migrateAllLegacyWidgetInformation(Context context) {
         final int[] currentWidgetIds = getCurrentWidgetIds(context);
         migrateLegacyWidgets(context, currentWidgetIds);
     }
 
-    private final void migrateLegacyWidgets(Context context, int[] widgetIds) {
+    private void migrateLegacyWidgets(Context context, int[] widgetIds) {
         for (int widgetId : widgetIds) {
             // We only want to bother to attempt to upgrade a widget if we don't already
             // have information about.
@@ -398,7 +397,7 @@
         }
     }
 
-    private final void validateAllWidgetInformation(Context context) {
+    private void validateAllWidgetInformation(Context context) {
         final int[] widgetIds = getCurrentWidgetIds(context);
         for (int widgetId : widgetIds) {
             final String accountFolder = MailPrefs.get(context).getWidgetConfiguration(widgetId);
diff --git a/src/com/android/mail/widget/WidgetService.java b/src/com/android/mail/widget/WidgetService.java
index daf57ce..7ad6095 100644
--- a/src/com/android/mail/widget/WidgetService.java
+++ b/src/com/android/mail/widget/WidgetService.java
@@ -60,7 +60,7 @@
     /**
      * Lock to avoid race condition between widgets.
      */
-    private static Object sWidgetLock = new Object();
+    private static final Object sWidgetLock = new Object();
 
     private static final String LOG_TAG = LogTag.getLogTag();
 
@@ -202,6 +202,7 @@
 
         private static final int FOLDER_LOADER_ID = 0;
         private static final int CONVERSATION_CURSOR_LOADER_ID = 1;
+        private static final int ACCOUNT_LOADER_ID = 2;
 
         private final Context mContext;
         private final int mAppWidgetId;
@@ -214,6 +215,7 @@
         private CursorLoader mConversationCursorLoader;
         private Cursor mConversationCursor;
         private CursorLoader mFolderLoader;
+        private CursorLoader mAccountLoader;
         private FolderUpdateHandler mFolderUpdateHandler;
         private int mFolderCount;
         private boolean mShouldShowViewMore;
@@ -232,8 +234,8 @@
             mFolderType = intent.getIntExtra(WidgetProvider.EXTRA_FOLDER_TYPE, FolderType.DEFAULT);
             mFolderDisplayName = intent.getStringExtra(WidgetProvider.EXTRA_FOLDER_DISPLAY_NAME);
 
-            Uri folderUri = intent.getParcelableExtra(WidgetProvider.EXTRA_FOLDER_URI);
-            Uri folderConversationListUri =
+            final Uri folderUri = intent.getParcelableExtra(WidgetProvider.EXTRA_FOLDER_URI);
+            final Uri folderConversationListUri =
                     intent.getParcelableExtra(WidgetProvider.EXTRA_FOLDER_CONVERSATION_LIST_URI);
             if (folderUri != null && folderConversationListUri != null) {
                 mFolderUri = folderUri;
@@ -241,6 +243,7 @@
             } else {
                 // This is a old intent created in version UR8 (or earlier).
                 String folderString = intent.getStringExtra(Utils.EXTRA_FOLDER);
+                //noinspection deprecation
                 Folder folder = Folder.fromString(folderString);
                 if (folder != null) {
                     mFolderUri = folder.folderUri.fullUri;
@@ -304,6 +307,10 @@
                     res.getInteger(R.integer.widget_folder_refresh_delay_ms));
             mFolderUpdateHandler.scheduleTask();
 
+            mAccountLoader = new CursorLoader(mContext, mAccount.uri,
+                    UIProvider.ACCOUNTS_PROJECTION_NO_CAPABILITIES, null, null, null);
+            mAccountLoader.registerListener(ACCOUNT_LOADER_ID, this);
+            mAccountLoader.startLoading();
         }
 
         @Override
@@ -325,6 +332,12 @@
                 mFolderLoader.unregisterListener(this);
                 mFolderLoader = null;
             }
+
+            if (mAccountLoader != null) {
+                mAccountLoader.reset();
+                mAccountLoader.unregisterListener(this);
+                mAccountLoader = null;
+            }
         }
 
         @Override
@@ -360,7 +373,7 @@
          * Returns the number of conversations that should be shown in the widget.  This method
          * doesn't update the boolean that indicates that the "show more" item should be included
          * in the list.
-         * @return
+         * @return count
          */
         private int getConversationCount() {
             synchronized (sWidgetLock) {
@@ -519,6 +532,11 @@
             final RemoteViews remoteViews =
                     new RemoteViews(mContext.getPackageName(), R.layout.widget);
 
+            if (!mService.isWidgetConfigured(mContext, mAppWidgetId, mAccount)) {
+                BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderType,
+                        mFolderUri, mFolderConversationListUri, mFolderDisplayName);
+            }
+
             if (loader == mFolderLoader) {
                 if (!isDataValid(data)) {
                     return;
@@ -586,6 +604,9 @@
                             mContext.getString(R.string.no_conversations));
                     appWidgetManager.partiallyUpdateAppWidget(mAppWidgetId, remoteViews);
                 }
+            } else if (loader == mAccountLoader) {
+                BaseWidgetProvider.updateWidget(mContext, mAppWidgetId, mAccount, mFolderType,
+                        mFolderUri, mFolderConversationListUri, mFolderDisplayName);
             }
         }