Merge "Improve BT slice card loading performance" into rvc-dev
diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java
index c0527c9..ebfaca9 100644
--- a/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java
+++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsFragment.java
@@ -37,6 +37,7 @@
 
 import com.android.settings.R;
 import com.android.settings.core.InstrumentedFragment;
+import com.android.settings.homepage.contextualcards.slices.BluetoothUpdateWorker;
 import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.wifi.slice.ContextualWifiScanWorker;
@@ -67,6 +68,7 @@
         final Context context = getContext();
         if (savedInstanceState == null) {
             FeatureFactory.getFactory(context).getSlicesFeatureProvider().newUiSession();
+            BluetoothUpdateWorker.initLocalBtManager(getContext());
         }
         mContextualCardManager = new ContextualCardManager(context, getSettingsLifecycle(),
                 savedInstanceState);
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
index 8b74313..19de403 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothDevicesSlice.java
@@ -81,15 +81,12 @@
     private static boolean sBluetoothEnabling;
 
     private final Context mContext;
-    private final AvailableMediaBluetoothDeviceUpdater mAvailableMediaBtDeviceUpdater;
-    private final SavedBluetoothDeviceUpdater mSavedBtDeviceUpdater;
+    private AvailableMediaBluetoothDeviceUpdater mAvailableMediaBtDeviceUpdater;
+    private SavedBluetoothDeviceUpdater mSavedBtDeviceUpdater;
 
     public BluetoothDevicesSlice(Context context) {
         mContext = context;
-        mAvailableMediaBtDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(mContext,
-                null /* fragment */, null /* devicePreferenceCallback */);
-        mSavedBtDeviceUpdater = new SavedBluetoothDeviceUpdater(mContext,
-                null /* fragment */, null /* devicePreferenceCallback */);
+        BluetoothUpdateWorker.initLocalBtManager(context);
     }
 
     @Override
@@ -119,16 +116,8 @@
         // Add the header of Bluetooth on
         listBuilder.addRow(getBluetoothOnHeader());
 
-        // Get row builders of Bluetooth devices.
-        final List<ListBuilder.RowBuilder> rows = getBluetoothRowBuilders();
-
-        // Determine the displayable row count.
-        final int displayableCount = Math.min(rows.size(), DEFAULT_EXPANDED_ROW_COUNT);
-
-        // Add device rows up to the count.
-        for (int i = 0; i < displayableCount; i++) {
-            listBuilder.addRow(rows.get(i));
-        }
+        // Add row builders of Bluetooth devices.
+        getBluetoothRowBuilders().forEach(row -> listBuilder.addRow(row));
 
         return listBuilder.build();
     }
@@ -181,19 +170,18 @@
     List<CachedBluetoothDevice> getPairedBluetoothDevices() {
         final List<CachedBluetoothDevice> bluetoothDeviceList = new ArrayList<>();
 
-        // If Bluetooth is disable, skip to get the Bluetooth devices.
+        // If Bluetooth is disable, skip getting the Bluetooth devices.
         if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
             Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is disabled.");
             return bluetoothDeviceList;
         }
 
-        // Get the Bluetooth devices from LocalBluetoothManager.
-        final LocalBluetoothManager localBtManager =
-                com.android.settings.bluetooth.Utils.getLocalBtManager(mContext);
+        final LocalBluetoothManager localBtManager = BluetoothUpdateWorker.getLocalBtManager();
         if (localBtManager == null) {
-            Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is unsupported.");
+            Log.i(TAG, "Cannot get Bluetooth devices, Bluetooth is not ready.");
             return bluetoothDeviceList;
         }
+
         final Collection<CachedBluetoothDevice> cachedDevices =
                 localBtManager.getCachedDeviceManager().getCachedDevicesCopy();
 
@@ -292,8 +280,21 @@
 
     private List<ListBuilder.RowBuilder> getBluetoothRowBuilders() {
         final List<ListBuilder.RowBuilder> bluetoothRows = new ArrayList<>();
+        final List<CachedBluetoothDevice> pairedDevices = getPairedBluetoothDevices();
+        if (pairedDevices.isEmpty()) {
+            return bluetoothRows;
+        }
+
+        // Initialize updaters without being blocked after paired devices is available because
+        // LocalBluetoothManager is ready.
+        lazyInitUpdaters();
+
         // Create row builders based on paired devices.
-        for (CachedBluetoothDevice device : getPairedBluetoothDevices()) {
+        for (CachedBluetoothDevice device : pairedDevices) {
+            if (bluetoothRows.size() >= DEFAULT_EXPANDED_ROW_COUNT) {
+                break;
+            }
+
             String summary = device.getConnectionSummary();
             if (summary == null) {
                 summary = mContext.getString(
@@ -321,6 +322,18 @@
         return bluetoothRows;
     }
 
+    private void lazyInitUpdaters() {
+        if (mAvailableMediaBtDeviceUpdater == null) {
+            mAvailableMediaBtDeviceUpdater = new AvailableMediaBluetoothDeviceUpdater(mContext,
+                    null /* fragment */, null /* devicePreferenceCallback */);
+        }
+
+        if (mSavedBtDeviceUpdater == null) {
+            mSavedBtDeviceUpdater = new SavedBluetoothDeviceUpdater(mContext,
+                    null /* fragment */, null /* devicePreferenceCallback */);
+        }
+    }
+
     @VisibleForTesting
     SliceAction buildPrimaryBluetoothAction(CachedBluetoothDevice bluetoothDevice) {
         final Intent intent = new Intent(getUri().toString())
diff --git a/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
index 1602f56..8f15f2c 100644
--- a/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
+++ b/src/com/android/settings/homepage/contextualcards/slices/BluetoothUpdateWorker.java
@@ -18,9 +18,14 @@
 
 import android.content.Context;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
 import android.util.Log;
 
-import com.android.settings.bluetooth.Utils;
+import androidx.annotation.Nullable;
+
 import com.android.settings.slices.SliceBackgroundWorker;
 import com.android.settingslib.bluetooth.BluetoothCallback;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -30,29 +35,46 @@
 
     private static final String TAG = "BluetoothUpdateWorker";
 
-    private final LocalBluetoothManager mLocalBluetoothManager;
+    private static LocalBluetoothManager sLocalBluetoothManager;
+
+    private LoadBtManagerHandler mLoadBtManagerHandler;
 
     public BluetoothUpdateWorker(Context context, Uri uri) {
         super(context, uri);
-        mLocalBluetoothManager = Utils.getLocalBtManager(context);
+        mLoadBtManagerHandler = LoadBtManagerHandler.getInstance(context);
+        if (sLocalBluetoothManager == null) {
+            mLoadBtManagerHandler.startLoadingBtManager(this);
+        }
+    }
+
+    /** Initialize {@link LocalBluetoothManager} in the background */
+    public static void initLocalBtManager(Context context) {
+        if (sLocalBluetoothManager == null) {
+            LoadBtManagerHandler.getInstance(context).startLoadingBtManager();
+        }
+    }
+
+    @Nullable
+    static LocalBluetoothManager getLocalBtManager() {
+        return sLocalBluetoothManager;
     }
 
     @Override
     protected void onSlicePinned() {
-        if (mLocalBluetoothManager == null) {
-            Log.i(TAG, "onSlicePinned() Bluetooth is unsupported.");
+        final LocalBluetoothManager localBtManager = mLoadBtManagerHandler.getLocalBtManager();
+        if (localBtManager == null) {
             return;
         }
-        mLocalBluetoothManager.getEventManager().registerCallback(this);
+        localBtManager.getEventManager().registerCallback(this);
     }
 
     @Override
     protected void onSliceUnpinned() {
-        if (mLocalBluetoothManager == null) {
-            Log.i(TAG, "onSliceUnpinned() Bluetooth is unsupported.");
+        final LocalBluetoothManager localBtManager = mLoadBtManagerHandler.getLocalBtManager();
+        if (localBtManager == null) {
             return;
         }
-        mLocalBluetoothManager.getEventManager().unregisterCallback(this);
+        localBtManager.getEventManager().unregisterCallback(this);
     }
 
     @Override
@@ -84,4 +106,59 @@
             int bluetoothProfile) {
         notifySliceChange();
     }
+
+    private static class LoadBtManagerHandler extends Handler {
+
+        private static LoadBtManagerHandler sHandler;
+
+        private final Runnable mLoadBtManagerTask;
+        private final Context mContext;
+        private BluetoothUpdateWorker mWorker;
+
+        private static LoadBtManagerHandler getInstance(Context context) {
+            if (sHandler == null) {
+                final HandlerThread workerThread = new HandlerThread(TAG,
+                        Process.THREAD_PRIORITY_BACKGROUND);
+                workerThread.start();
+                sHandler = new LoadBtManagerHandler(context, workerThread.getLooper());
+            }
+            return sHandler;
+        }
+
+        private LoadBtManagerHandler(Context context, Looper looper) {
+            super(looper);
+            mContext = context;
+            mLoadBtManagerTask = () -> {
+                Log.d(TAG, "LoadBtManagerHandler: start loading...");
+                final long startTime = System.currentTimeMillis();
+                sLocalBluetoothManager = getLocalBtManager();
+                Log.d(TAG, "LoadBtManagerHandler took " + (System.currentTimeMillis() - startTime)
+                        + " ms");
+            };
+        }
+
+        private LocalBluetoothManager getLocalBtManager() {
+            if (sLocalBluetoothManager != null) {
+                return sLocalBluetoothManager;
+            }
+            return LocalBluetoothManager.getInstance(mContext,
+                    (context, btManager) -> {
+                        if (mWorker != null) {
+                            // notify change if the worker is ready
+                            mWorker.notifySliceChange();
+                        }
+                    });
+        }
+
+        private void startLoadingBtManager() {
+            if (!hasCallbacks(mLoadBtManagerTask)) {
+                post(mLoadBtManagerTask);
+            }
+        }
+
+        private void startLoadingBtManager(BluetoothUpdateWorker worker) {
+            mWorker = worker;
+            startLoadingBtManager();
+        }
+    }
 }
\ No newline at end of file