Improve settings cold start latency.

- Move view initialization from onViewCreated to onCreateView. This
  doesn't really improve anything, it simply aligns the code more
  with view's lifecycle management.
- Move dashboard category init into background. The init contains logic
  invoking packageManager, which can be very expensive.
  - Remove any call to DashboardFeatureProvider from SummaryLoader, and
    delay the getCategory call until someone calls setListener().
  - call updateCategory() from background thread.

Test: rerun app launch test. Avg latency drops back to pre-suggestion-v2
      level.
Test: robotest
Fixes: 68761512

Change-Id: I5ec85af08e7b610786e439bda93b3651f5975593
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index b7c8715..8f66b67 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -24,6 +24,7 @@
 import android.os.Handler;
 import android.service.settings.suggestions.Suggestion;
 import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
 import android.support.v7.widget.LinearLayoutManager;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -50,6 +51,7 @@
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.suggestions.SuggestionList;
 import com.android.settingslib.suggestions.SuggestionParser;
+import com.android.settingslib.utils.ThreadUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -182,12 +184,6 @@
     }
 
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.dashboard, container, false);
-    }
-
-    @Override
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         if (mLayoutManager == null) return;
@@ -198,9 +194,10 @@
     }
 
     @Override
-    public void onViewCreated(View view, Bundle bundle) {
+    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
         long startTime = System.currentTimeMillis();
-        mDashboard = view.findViewById(R.id.dashboard_container);
+        final View root = inflater.inflate(R.layout.dashboard, container, false);
+        mDashboard = root.findViewById(R.id.dashboard_container);
         mLayoutManager = new LinearLayoutManager(getContext());
         mLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
         if (bundle != null) {
@@ -218,19 +215,19 @@
         mSummaryLoader.setSummaryConsumer(mAdapter);
         ActionBarShadowController.attachToRecyclerView(
                 getActivity().findViewById(R.id.search_bar_container), getLifecycle(), mDashboard);
-
+        rebuildUI();
         if (DEBUG_TIMING) {
-            Log.d(TAG, "onViewCreated took "
+            Log.d(TAG, "onCreateView took "
                     + (System.currentTimeMillis() - startTime) + " ms");
         }
-        rebuildUI();
+        return root;
     }
 
     @VisibleForTesting
     void rebuildUI() {
         if (!mSuggestionFeatureProvider.isSuggestionEnabled(getContext())) {
             Log.d(TAG, "Suggestion v1 feature is disabled, skipping suggestion v1");
-            updateCategory();
+            ThreadUtils.postOnBackgroundThread(() -> updateCategory());
         } else {
             new SuggestionLoader().execute();
             // Set categories on their own if loading suggestions takes too long.
@@ -340,11 +337,12 @@
         }
     }
 
+    @WorkerThread
     void updateCategory() {
         final DashboardCategory category = mDashboardFeatureProvider.getTilesForCategory(
                 CategoryKey.CATEGORY_HOMEPAGE);
         mSummaryLoader.updateSummaryToCache(category);
-        mAdapter.setCategory(category);
+        ThreadUtils.postOnMainThread(() -> mAdapter.setCategory(category));
     }
 
     /**
diff --git a/src/com/android/settings/dashboard/SummaryLoader.java b/src/com/android/settings/dashboard/SummaryLoader.java
index 5816bba8..fe55be8 100644
--- a/src/com/android/settings/dashboard/SummaryLoader.java
+++ b/src/com/android/settings/dashboard/SummaryLoader.java
@@ -60,23 +60,6 @@
     private boolean mWorkerListening;
     private ArraySet<BroadcastReceiver> mReceivers = new ArraySet<>();
 
-    public SummaryLoader(Activity activity, List<DashboardCategory> categories) {
-        mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
-                .getDashboardFeatureProvider(activity);
-        mCategoryKey = null;
-        mWorkerThread = new HandlerThread("SummaryLoader", Process.THREAD_PRIORITY_BACKGROUND);
-        mWorkerThread.start();
-        mWorker = new Worker(mWorkerThread.getLooper());
-        mActivity = activity;
-        for (int i = 0; i < categories.size(); i++) {
-            List<Tile> tiles = categories.get(i).tiles;
-            for (int j = 0; j < tiles.size(); j++) {
-                Tile tile = tiles.get(j);
-                mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
-            }
-        }
-    }
-
     public SummaryLoader(Activity activity, String categoryKey) {
         mDashboardFeatureProvider = FeatureFactory.getFactory(activity)
                 .getDashboardFeatureProvider(activity);
@@ -85,17 +68,6 @@
         mWorkerThread.start();
         mWorker = new Worker(mWorkerThread.getLooper());
         mActivity = activity;
-
-        final DashboardCategory category =
-                mDashboardFeatureProvider.getTilesForCategory(categoryKey);
-        if (category == null || category.tiles == null) {
-            return;
-        }
-
-        List<Tile> tiles = category.tiles;
-        for (Tile tile : tiles) {
-            mWorker.obtainMessage(Worker.MSG_GET_PROVIDER, tile).sendToTarget();
-        }
     }
 
     public void release() {
@@ -153,15 +125,32 @@
      * Only call from the main thread.
      */
     public void setListening(boolean listening) {
-        if (mListening == listening) return;
+        if (mListening == listening) {
+            return;
+        }
         mListening = listening;
         // Unregister listeners immediately.
         for (int i = 0; i < mReceivers.size(); i++) {
             mActivity.unregisterReceiver(mReceivers.valueAt(i));
         }
         mReceivers.clear();
+
         mWorker.removeMessages(Worker.MSG_SET_LISTENING);
-        mWorker.obtainMessage(Worker.MSG_SET_LISTENING, listening ? 1 : 0, 0).sendToTarget();
+        if (!listening) {
+            // Stop listen
+            mWorker.obtainMessage(Worker.MSG_SET_LISTENING, 0 /* listening */).sendToTarget();
+        } else {
+            // Start listen
+            if (mSummaryProviderMap.isEmpty()) {
+                // Category not initialized yet, init before starting to listen
+                if (!mWorker.hasMessages(Worker.MSG_GET_CATEGORY_TILES_AND_SET_LISTENING)) {
+                    mWorker.sendEmptyMessage(Worker.MSG_GET_CATEGORY_TILES_AND_SET_LISTENING);
+                }
+            } else {
+                // Category already initialized, start listening immediately
+                mWorker.obtainMessage(Worker.MSG_SET_LISTENING, 1 /* listening */).sendToTarget();
+            }
+        }
     }
 
     private SummaryProvider getSummaryProvider(Tile tile) {
@@ -236,9 +225,13 @@
     }
 
     private synchronized void setListeningW(boolean listening) {
-        if (mWorkerListening == listening) return;
+        if (mWorkerListening == listening) {
+            return;
+        }
         mWorkerListening = listening;
-        if (DEBUG) Log.d(TAG, "Listening " + listening);
+        if (DEBUG) {
+            Log.d(TAG, "Listening " + listening);
+        }
         for (SummaryProvider p : mSummaryProviderMap.keySet()) {
             try {
                 p.setListening(listening);
@@ -271,7 +264,6 @@
     }
 
 
-
     public interface SummaryProvider {
         void setListening(boolean listening);
     }
@@ -285,8 +277,9 @@
     }
 
     private class Worker extends Handler {
-        private static final int MSG_GET_PROVIDER = 1;
-        private static final int MSG_SET_LISTENING = 2;
+        private static final int MSG_GET_CATEGORY_TILES_AND_SET_LISTENING = 1;
+        private static final int MSG_GET_PROVIDER = 2;
+        private static final int MSG_SET_LISTENING = 3;
 
         public Worker(Looper looper) {
             super(looper);
@@ -295,6 +288,18 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MSG_GET_CATEGORY_TILES_AND_SET_LISTENING:
+                    final DashboardCategory category =
+                            mDashboardFeatureProvider.getTilesForCategory(mCategoryKey);
+                    if (category == null || category.tiles == null) {
+                        return;
+                    }
+                    final List<Tile> tiles = category.tiles;
+                    for (Tile tile : tiles) {
+                        makeProviderW(tile);
+                    }
+                    setListeningW(true);
+                    break;
                 case MSG_GET_PROVIDER:
                     Tile tile = (Tile) msg.obj;
                     makeProviderW(tile);
diff --git a/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java b/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
index 146be9c..44b6139 100644
--- a/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/dashboard/SummaryLoaderTest.java
@@ -16,6 +16,10 @@
 
 package com.android.settings.dashboard;
 
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
@@ -23,6 +27,7 @@
 import com.android.settings.TestConfig;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
+import com.android.settingslib.drawer.CategoryKey;
 import com.android.settingslib.drawer.DashboardCategory;
 import com.android.settingslib.drawer.Tile;
 
@@ -35,12 +40,6 @@
 import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.when;
-
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class SummaryLoaderTest {
@@ -65,14 +64,14 @@
         mCallbackInvoked = false;
 
         final Activity activity = Robolectric.buildActivity(Activity.class).get();
-        final List<DashboardCategory> categories = new ArrayList<>();
-        mSummaryLoader = new SummaryLoader(activity, categories);
-        mSummaryLoader.setSummaryConsumer(new SummaryLoader.SummaryConsumer() {
-            @Override
-            public void notifySummaryChanged(Tile tile) {
-                mCallbackInvoked = true;
-            }
-        });
+
+        mSummaryLoader = new SummaryLoader(activity, CategoryKey.CATEGORY_HOMEPAGE);
+        mSummaryLoader.setSummaryConsumer(tile -> mCallbackInvoked = true);
+    }
+
+    @Test
+    public void newInstance_shouldNotLoadCategory() {
+        verifyZeroInteractions(mFeatureFactory.dashboardFeatureProvider);
     }
 
     @Test