Merge "Query the security feature provider on separate threads."
diff --git a/src/com/android/settings/SecuritySettings.java b/src/com/android/settings/SecuritySettings.java
index ec2235d..7417516 100644
--- a/src/com/android/settings/SecuritySettings.java
+++ b/src/com/android/settings/SecuritySettings.java
@@ -76,6 +76,8 @@
 import com.android.settingslib.drawer.TileUtils;
 
 import java.util.ArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
 import java.util.List;
 
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
@@ -937,8 +939,8 @@
                         MY_USER_ID));
             }
             if (mPowerButtonInstantlyLocks != null) {
-                mPowerButtonInstantlyLocks.setChecked(mLockPatternUtils.getPowerButtonInstantlyLocks(
-                        MY_USER_ID));
+                mPowerButtonInstantlyLocks.setChecked(
+                        mLockPatternUtils.getPowerButtonInstantlyLocks(MY_USER_ID));
             }
 
             updateOwnerInfo();
@@ -1202,9 +1204,18 @@
                     FeatureFactory.getFactory(mContext).getDashboardFeatureProvider(mContext);
             if (dashboardFeatureProvider.isEnabled()
                     && (packageVerifierState == PACKAGE_VERIFIER_STATE_ENABLED)) {
-                DashboardCategory dashboardCategory =
-                        dashboardFeatureProvider.getTilesForCategory(CategoryKey.CATEGORY_SECURITY);
-                mSummaryLoader.setSummary(this, getPackageVerifierSummary(dashboardCategory));
+                // Calling the feature provider could potentially be slow, so do this on a separate
+                // thread so as to not block the loading of Settings.
+                Executors.newSingleThreadExecutor().execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        DashboardCategory dashboardCategory =
+                                dashboardFeatureProvider.getTilesForCategory(
+                                        CategoryKey.CATEGORY_SECURITY);
+                        mSummaryLoader.setSummary(SummaryProvider.this,
+                                getPackageVerifierSummary(dashboardCategory));
+                    }
+                });
             } else {
                 final FingerprintManager fpm = Utils.getFingerprintManagerOrNull(mContext);
                 if (fpm != null && fpm.isHardwareDetected()) {
diff --git a/src/com/android/settings/security/SecurityFeatureProviderImpl.java b/src/com/android/settings/security/SecurityFeatureProviderImpl.java
index 72d0f81..2d782a6 100644
--- a/src/com/android/settings/security/SecurityFeatureProviderImpl.java
+++ b/src/com/android/settings/security/SecurityFeatureProviderImpl.java
@@ -22,9 +22,12 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
 import com.android.settings.trustagent.TrustAgentManager;
 import com.android.settings.trustagent.TrustAgentManagerImpl;
 import com.android.settingslib.drawer.DashboardCategory;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
@@ -35,6 +38,7 @@
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.drawer.TileUtils;
 
+import java.util.concurrent.Executors;
 import java.util.Map;
 
 /** Implementation for {@code SecurityFeatureProvider}. */
@@ -43,8 +47,22 @@
     private TrustAgentManager mTrustAgentManager;
 
     /** Update preferences with data from associated tiles. */
-    public void updatePreferences(Context context, PreferenceScreen preferenceScreen,
+    public void updatePreferences(final Context context, final PreferenceScreen preferenceScreen,
+            final DashboardCategory dashboardCategory) {
+        // Fetching the summary and icon from the provider introduces latency, so do this on a
+        // separate thread.
+        Executors.newSingleThreadExecutor().execute(new Runnable() {
+            @Override
+            public void run() {
+                updatePreferencesToRunOnWorkerThread(context, preferenceScreen, dashboardCategory);
+            }
+        });
+    }
+
+    @VisibleForTesting
+    void updatePreferencesToRunOnWorkerThread(Context context, PreferenceScreen preferenceScreen,
             DashboardCategory dashboardCategory) {
+
         if (preferenceScreen == null) {
             return;
         }
@@ -82,26 +100,41 @@
                         TileUtils.getIconFromUri(context, packageName, iconUri, providerMap);
                 if (icon != null) {
                     // Icon is only returned if the icon belongs to Settings or the target app.
-                    try {
-                        matchingPref.setIcon(context.getPackageManager()
-                                .getResourcesForApplication(icon.first /* package name */)
-                                        .getDrawable(icon.second /* res id */, context.getTheme()));
-                    } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
-                        // Intentionally ignored. If icon resources cannot be found, do not update.
-                    }
+                    // setIcon must be called on the UI thread.
+                    new Handler(Looper.getMainLooper()).post(new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                matchingPref.setIcon(context.getPackageManager()
+                                        .getResourcesForApplication(icon.first /* package name */)
+                                                .getDrawable(icon.second /* res id */,
+                                                        context.getTheme()));
+                            } catch (PackageManager.NameNotFoundException
+                                    | Resources.NotFoundException e) {
+                                // Intentionally ignored. If icon resources cannot be found, do not
+                                // update.
+                            }
+                        }
+                    });
                 }
             }
             if (!TextUtils.isEmpty(summaryUri)) {
                 String summary = TileUtils.getTextFromUri(context, summaryUri, providerMap,
                         TileUtils.META_DATA_PREFERENCE_SUMMARY);
-                // Only update the summary if it has actually changed.
-                if (summary == null) {
-                    if (matchingPref.getSummary() != null) {
-                        matchingPref.setSummary(summary);
+                // setSummary must be called on UI thread.
+                new Handler(Looper.getMainLooper()).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Only update the summary if it has actually changed.
+                        if (summary == null) {
+                            if (matchingPref.getSummary() != null) {
+                                matchingPref.setSummary(summary);
+                            }
+                        } else if (!summary.equals(matchingPref.getSummary())) {
+                            matchingPref.setSummary(summary);
+                        }
                     }
-                } else if (!summary.equals(matchingPref.getSummary())) {
-                    matchingPref.setSummary(summary);
-                }
+                });
             }
         }
     }
diff --git a/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
index 293c0ae..49eb48b 100644
--- a/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/security/SecurityFeatureProviderImplTest.java
@@ -42,6 +42,7 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowLooper;
 
 import java.util.Map;
 
@@ -102,8 +103,11 @@
 
     @Test
     public void updateTilesData_shouldNotProcessEmptyScreenOrTiles() {
-        mImpl.updatePreferences(mContext, null, null);
-        mImpl.updatePreferences(mContext, new PreferenceScreen(mContext, null), null);
+        mImpl.updatePreferencesToRunOnWorkerThread(mContext, null, null);
+        ShadowLooper.runUiThreadTasks();
+        mImpl.updatePreferencesToRunOnWorkerThread(
+                mContext, new PreferenceScreen(mContext, null), null);
+        ShadowLooper.runUiThreadTasks();
         verifyNoMoreInteractions(mPackageManager);
     }
 
@@ -111,13 +115,17 @@
     public void updateTilesData_shouldNotProcessNonMatchingPreference() {
         DashboardCategory dashboardCategory = new DashboardCategory();
         dashboardCategory.addTile(new Tile());
-        mImpl.updatePreferences(mContext, getPreferenceScreen(), dashboardCategory);
+        mImpl.updatePreferencesToRunOnWorkerThread(
+                mContext, getPreferenceScreen(), dashboardCategory);
+        ShadowLooper.runUiThreadTasks();
         verifyNoMoreInteractions(mPackageManager);
     }
 
     @Test
     public void updateTilesData_shouldNotProcessMatchingPreferenceWithNoData() {
-        mImpl.updatePreferences(mContext, getPreferenceScreen(), getDashboardCategory());
+        mImpl.updatePreferencesToRunOnWorkerThread(
+                mContext, getPreferenceScreen(), getDashboardCategory());
+        ShadowLooper.runUiThreadTasks();
         verifyNoMoreInteractions(mPackageManager);
     }
 
@@ -135,7 +143,8 @@
         dashboardCategory.getTile(0).intent = new Intent().setPackage("package");
         dashboardCategory.getTile(0).metaData = bundle;
 
-        mImpl.updatePreferences(mContext, screen, dashboardCategory);
+        mImpl.updatePreferencesToRunOnWorkerThread(mContext, screen, dashboardCategory);
+        ShadowLooper.runUiThreadTasks();
         verify(screen.findPreference(MOCK_KEY)).setIcon(mMockDrawable);
         verify(screen.findPreference(MOCK_KEY)).setSummary(MOCK_SUMMARY);
     }
@@ -157,7 +166,8 @@
         dashboardCategory.getTile(0).intent = new Intent().setPackage("package");
         dashboardCategory.getTile(0).metaData = bundle;
 
-        mImpl.updatePreferences(mContext, screen, dashboardCategory);
+        mImpl.updatePreferencesToRunOnWorkerThread(mContext, screen, dashboardCategory);
+        ShadowLooper.runUiThreadTasks();
         verify(screen.findPreference(MOCK_KEY), never()).setSummary(anyString());
     }