Cache the summary and icons for Security Settings injected tiles.

This helps the summary and icons load more smoothly when resuming
from recents.

Bug: 35994047
Test: make RunSettingsRoboTests
Change-Id: I763f7dd19f64007959e1281f0ff04ecd19bbfbfc
(cherry picked from commit 45f4e1241d7ce15d381a1ea1e671e8eef17da94a)
diff --git a/src/com/android/settings/security/SecurityFeatureProviderImpl.java b/src/com/android/settings/security/SecurityFeatureProviderImpl.java
index 7be2d66..5a2ff2e 100644
--- a/src/com/android/settings/security/SecurityFeatureProviderImpl.java
+++ b/src/com/android/settings/security/SecurityFeatureProviderImpl.java
@@ -39,6 +39,7 @@
 import com.android.settingslib.drawer.TileUtils;
 
 import java.util.concurrent.Executors;
+import java.util.TreeMap;
 import java.util.Map;
 
 /** Implementation for {@code SecurityFeatureProvider}. */
@@ -51,6 +52,12 @@
     @VisibleForTesting
     static final String DEFAULT_SUMMARY = " ";
 
+    @VisibleForTesting
+    static Map<String, Pair<String, Integer>> sIconCache = new TreeMap<>();
+
+    @VisibleForTesting
+    static Map<String, String> sSummaryCache = new TreeMap<>();
+
     /** Update preferences with data from associated tiles. */
     public void updatePreferences(final Context context, final PreferenceScreen preferenceScreen,
             final DashboardCategory dashboardCategory) {
@@ -89,11 +96,33 @@
             if (matchingPref == null) {
                 continue;
             }
-            // Remove any icons that may be loaded before we inject the final icon.
-            matchingPref.setIcon(DEFAULT_ICON);
-            // Reserve room for the summary. This prevents the title from having to shift when the
-            // final title is injected.
-            matchingPref.setSummary(DEFAULT_SUMMARY);
+            // Either remove an icon by replacing them with nothing, or use the cached one since
+            // there is a delay in fetching the injected icon, and we don't want an inappropriate
+            // icon to be displayed while waiting for the injected icon.
+            final String iconUri =
+                    tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_URI, null);
+            Drawable drawable = DEFAULT_ICON;
+            if ((iconUri != null) && sIconCache.containsKey(iconUri)) {
+                Pair<String, Integer> icon = sIconCache.get(iconUri);
+                try {
+                    drawable = context.getPackageManager()
+                            .getResourcesForApplication(icon.first /* package name */)
+                                    .getDrawable(icon.second /* res id */,
+                                            context.getTheme());
+                } catch (PackageManager.NameNotFoundException e) {
+                    // Ignore and just load the default icon.
+                }
+            }
+            matchingPref.setIcon(drawable);
+            // Either reserve room for the summary or load the cached one. This prevents the title
+            // from shifting when the final summary is injected.
+            final String summaryUri =
+                    tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, null);
+            String summary = DEFAULT_SUMMARY;
+            if ((summaryUri != null) && sSummaryCache.containsKey(summaryUri)) {
+                summary = sSummaryCache.get(summaryUri);
+            }
+            matchingPref.setSummary(summary);
         }
     }
 
@@ -115,8 +144,9 @@
                 continue;
             }
             // Check if the tile has content providers for dynamically updatable content.
-            String iconUri = tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_URI, null);
-            String summaryUri =
+            final String iconUri =
+                    tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_ICON_URI, null);
+            final String summaryUri =
                     tile.metaData.getString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, null);
             if (!TextUtils.isEmpty(iconUri)) {
                 String packageName = null;
@@ -131,6 +161,7 @@
                 Pair<String, Integer> icon =
                         TileUtils.getIconFromUri(context, packageName, iconUri, providerMap);
                 if (icon != null) {
+                    sIconCache.put(iconUri, icon);
                     // Icon is only returned if the icon belongs to Settings or the target app.
                     // setIcon must be called on the UI thread.
                     new Handler(Looper.getMainLooper()).post(new Runnable() {
@@ -153,6 +184,7 @@
             if (!TextUtils.isEmpty(summaryUri)) {
                 String summary = TileUtils.getTextFromUri(context, summaryUri, providerMap,
                         TileUtils.META_DATA_PREFERENCE_SUMMARY);
+                sSummaryCache.put(summaryUri, summary);
                 // setSummary must be called on UI thread.
                 new Handler(Looper.getMainLooper()).post(new Runnable() {
                     @Override
diff --git a/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
index 50debb4..77f5ecc 100644
--- a/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
@@ -77,7 +77,7 @@
     private SecurityFeatureProviderImpl mImpl;
 
     @Implements(com.android.settingslib.drawer.TileUtils.class)
-    public static class TileUtilsMock {
+    public static class ShadowTileUtils {
         @Implementation
         public static Pair getIconFromUri(Context context, String packageName, String uriString,
                 Map<String, IContentProvider> providerMap) {
@@ -131,7 +131,7 @@
 
     @Test
     @Config(shadows = {
-            TileUtilsMock.class,
+            ShadowTileUtils.class,
     })
     public void updateTilesData_shouldUpdateMatchingPreference() {
         Bundle bundle = new Bundle();
@@ -151,7 +151,7 @@
 
     @Test
     @Config(shadows = {
-            TileUtilsMock.class,
+            ShadowTileUtils.class,
     })
     public void updateTilesData_shouldNotUpdateAlreadyUpdatedPreference() {
         Bundle bundle = new Bundle();
@@ -183,6 +183,31 @@
                 .setSummary(SecurityFeatureProviderImpl.DEFAULT_SUMMARY);
     }
 
+    @Test
+    @Config(shadows = {
+            ShadowTileUtils.class,
+    })
+    public void initPreferences_shouldLoadCached() {
+        Bundle bundle = new Bundle();
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_ICON_URI, URI_GET_ICON);
+        bundle.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, URI_GET_SUMMARY);
+
+        PreferenceScreen screen = getPreferenceScreen();
+        DashboardCategory dashboardCategory = getDashboardCategory();
+        dashboardCategory.getTile(0).metaData = bundle;
+
+        SecurityFeatureProviderImpl.sIconCache.put(
+                URI_GET_ICON,
+                ShadowTileUtils.getIconFromUri(null, null, null, null));
+        SecurityFeatureProviderImpl.sSummaryCache.put(
+                URI_GET_SUMMARY,
+                MOCK_SUMMARY);
+
+        mImpl.initPreferences(mContext, screen, dashboardCategory);
+        verify(screen.findPreference(MOCK_KEY)).setIcon(mMockDrawable);
+        verify(screen.findPreference(MOCK_KEY)).setSummary(MOCK_SUMMARY);
+    }
+
     private PreferenceScreen getPreferenceScreen() {
         final PreferenceScreen screen = mock(PreferenceScreen.class);
         final Preference pref = mock(Preference.class);