[Pair hearing devices] Extract common behavior in BluetoothPairingDetail into Base class

* Before adding new hearing device feature, we will extract the common part into Base class first. They will share most of the UI component.

Bug: 237625815
Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDevicePairingDetailBaseTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=BluetoothDevicePairingDetailTest
Change-Id: I3a44c4c464d630fdcafa151afc82d3000fd728a3
diff --git a/res/xml/bluetooth_pairing_detail.xml b/res/xml/bluetooth_pairing_detail.xml
index 86fb9e4..460a708 100644
--- a/res/xml/bluetooth_pairing_detail.xml
+++ b/res/xml/bluetooth_pairing_detail.xml
@@ -27,7 +27,7 @@
 
     <com.android.settings.bluetooth.BluetoothProgressCategory
         android:key="available_devices"
-        android:title="@string/bluetooth_paired_device_title"/>
+        android:title="@string/bluetooth_preference_found_media_devices"/>
 
     <com.android.settingslib.widget.FooterPreference/>
 
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
new file mode 100644
index 0000000..c71decb
--- /dev/null
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.accessibility.AccessibilityStatsLogUtils;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
+
+/**
+ * Abstract class for providing basic interaction for a list of Bluetooth devices in bluetooth
+ * device pairing detail page.
+ */
+public abstract class BluetoothDevicePairingDetailBase extends DeviceListPreferenceFragment {
+
+    protected boolean mInitialScanStarted;
+    @VisibleForTesting
+    protected BluetoothProgressCategory mAvailableDevicesCategory;
+
+    public BluetoothDevicePairingDetailBase() {
+        super(DISALLOW_CONFIG_BLUETOOTH);
+    }
+
+    @Override
+    public void initPreferencesFromPreferenceScreen() {
+        mAvailableDevicesCategory = findPreference(getDeviceListKey());
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        mInitialScanStarted = false;
+        super.onViewCreated(view, savedInstanceState);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (mLocalManager == null) {
+            Log.e(getLogTag(), "Bluetooth is not supported on this device");
+            return;
+        }
+        updateBluetooth();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        if (mLocalManager == null) {
+            Log.e(getLogTag(), "Bluetooth is not supported on this device");
+            return;
+        }
+        disableScanning();
+    }
+
+    @Override
+    public void onBluetoothStateChanged(int bluetoothState) {
+        super.onBluetoothStateChanged(bluetoothState);
+        updateContent(bluetoothState);
+        if (bluetoothState == BluetoothAdapter.STATE_ON) {
+            showBluetoothTurnedOnToast();
+        }
+    }
+
+    @Override
+    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
+        if (bondState == BluetoothDevice.BOND_BONDED) {
+            // If one device is connected(bonded), then close this fragment.
+            finish();
+            return;
+        } else if (bondState == BluetoothDevice.BOND_BONDING) {
+            // Set the bond entry where binding process starts for logging hearing aid device info
+            final int pageId = FeatureFactory.getFactory(
+                    getContext()).getMetricsFeatureProvider().getAttribution(getActivity());
+            final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
+                    pageId);
+            HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
+        }
+        if (mSelectedDevice != null && cachedDevice != null) {
+            BluetoothDevice device = cachedDevice.getDevice();
+            if (device != null && mSelectedDevice.equals(device)
+                    && bondState == BluetoothDevice.BOND_NONE) {
+                // If currently selected device failed to bond, restart scanning
+                enableScanning();
+            }
+        }
+    }
+
+    @Override
+    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
+            int bluetoothProfile) {
+        // This callback is used to handle the case that bonded device is connected in pairing list.
+        // 1. If user selected multiple bonded devices in pairing list, after connected
+        // finish this page.
+        // 2. If the bonded devices auto connected in paring list, after connected it will be
+        // removed from paring list.
+        if (cachedDevice != null && cachedDevice.isConnected()) {
+            final BluetoothDevice device = cachedDevice.getDevice();
+            if (device != null && mSelectedList.contains(device)) {
+                finish();
+            } else if (mDevicePreferenceMap.containsKey(cachedDevice)) {
+                onDeviceDeleted(cachedDevice);
+            }
+        }
+    }
+
+    @Override
+    public void enableScanning() {
+        // Clear all device states before first scan
+        if (!mInitialScanStarted) {
+            if (mAvailableDevicesCategory != null) {
+                removeAllDevices();
+            }
+            mLocalManager.getCachedDeviceManager().clearNonBondedDevices();
+            mInitialScanStarted = true;
+        }
+        super.enableScanning();
+    }
+
+    @Override
+    public void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
+        disableScanning();
+        super.onDevicePreferenceClick(btPreference);
+    }
+
+    @VisibleForTesting
+    void updateBluetooth() {
+        if (mBluetoothAdapter.isEnabled()) {
+            updateContent(mBluetoothAdapter.getState());
+        } else {
+            // Turn on bluetooth if it is disabled
+            mBluetoothAdapter.enable();
+        }
+    }
+
+    /**
+     * Enables the scanning when {@code bluetoothState} is on, or finish the page when
+     * {@code bluetoothState} is off.
+     *
+     * @param bluetoothState the current Bluetooth state, the possible values that will handle here:
+     * {@link android.bluetooth.BluetoothAdapter#STATE_OFF},
+     * {@link android.bluetooth.BluetoothAdapter#STATE_ON},
+     */
+    @VisibleForTesting
+    public void updateContent(int bluetoothState) {
+        switch (bluetoothState) {
+            case BluetoothAdapter.STATE_ON:
+                mDevicePreferenceMap.clear();
+                clearPreferenceGroupCache();
+                mBluetoothAdapter.enable();
+                enableScanning();
+                break;
+
+            case BluetoothAdapter.STATE_OFF:
+                finish();
+                break;
+        }
+    }
+
+    /**
+     * Clears all cached preferences in {@code preferenceGroup}.
+     */
+    private void clearPreferenceGroupCache() {
+        cacheRemoveAllPrefs(mAvailableDevicesCategory);
+        removeCachedPrefs(mAvailableDevicesCategory);
+    }
+
+    @VisibleForTesting
+    void showBluetoothTurnedOnToast() {
+        Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
+                Toast.LENGTH_SHORT).show();
+    }
+}
diff --git a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
index 9a92783..a78bf27 100644
--- a/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
+++ b/src/com/android/settings/bluetooth/BluetoothPairingDetail.java
@@ -16,31 +16,25 @@
 
 package com.android.settings.bluetooth;
 
-import static android.os.UserManager.DISALLOW_CONFIG_BLUETOOTH;
-
 import android.app.settings.SettingsEnums;
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
+import android.view.View;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.R;
-import com.android.settings.accessibility.AccessibilityStatsLogUtils;
-import com.android.settings.overlay.FeatureFactory;
 import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.HearingAidStatsLogUtils;
 import com.android.settingslib.search.Indexable;
 import com.android.settingslib.widget.FooterPreference;
 
 /**
  * BluetoothPairingDetail is a page to scan bluetooth devices and pair them.
  */
-public class BluetoothPairingDetail extends DeviceListPreferenceFragment implements
+public class BluetoothPairingDetail extends BluetoothDevicePairingDetailBase implements
         Indexable {
     private static final String TAG = "BluetoothPairingDetail";
 
@@ -50,34 +44,12 @@
     static final String KEY_FOOTER_PREF = "footer_preference";
 
     @VisibleForTesting
-    BluetoothProgressCategory mAvailableDevicesCategory;
-    @VisibleForTesting
     FooterPreference mFooterPreference;
     @VisibleForTesting
     AlwaysDiscoverable mAlwaysDiscoverable;
 
-    private boolean mInitialScanStarted;
-
     public BluetoothPairingDetail() {
-        super(DISALLOW_CONFIG_BLUETOOTH);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-        mInitialScanStarted = false;
-        mAlwaysDiscoverable = new AlwaysDiscoverable(getContext());
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        if (mLocalManager == null){
-            Log.e(TAG, "Bluetooth is not supported on this device");
-            return;
-        }
-        updateBluetooth();
-        mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering());
+        super();
     }
 
     @Override
@@ -86,32 +58,29 @@
         use(BluetoothDeviceRenamePreferenceController.class).setFragment(this);
     }
 
-    @VisibleForTesting
-    void updateBluetooth() {
-        if (mBluetoothAdapter.isEnabled()) {
-            updateContent(mBluetoothAdapter.getState());
-        } else {
-            // Turn on bluetooth if it is disabled
-            mBluetoothAdapter.enable();
-        }
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        mAlwaysDiscoverable = new AlwaysDiscoverable(getContext());
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mAvailableDevicesCategory.setProgress(mBluetoothAdapter.isDiscovering());
     }
 
     @Override
     public void onStop() {
         super.onStop();
-        if (mLocalManager == null){
-            Log.e(TAG, "Bluetooth is not supported on this device");
-            return;
-        }
         // Make the device only visible to connected devices.
         mAlwaysDiscoverable.stop();
-        disableScanning();
     }
 
     @Override
-    void initPreferencesFromPreferenceScreen() {
-        mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_AVAIL_DEVICES);
-        mFooterPreference = (FooterPreference) findPreference(KEY_FOOTER_PREF);
+    public void initPreferencesFromPreferenceScreen() {
+        super.initPreferencesFromPreferenceScreen();
+        mFooterPreference = findPreference(KEY_FOOTER_PREF);
         mFooterPreference.setSelectable(false);
     }
 
@@ -120,23 +89,25 @@
         return SettingsEnums.BLUETOOTH_PAIRING;
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * Will update footer and keep the device discoverable as long as the page is visible.
+     */
+    @VisibleForTesting
     @Override
-    void enableScanning() {
-        // Clear all device states before first scan
-        if (!mInitialScanStarted) {
-            if (mAvailableDevicesCategory != null) {
-                removeAllDevices();
+    public void updateContent(int bluetoothState) {
+        super.updateContent(bluetoothState);
+        if (bluetoothState == BluetoothAdapter.STATE_ON) {
+            if (mInitialScanStarted) {
+                // Don't show bonded devices when screen turned back on
+                setFilter(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER);
+                addCachedDevices();
             }
-            mLocalManager.getCachedDeviceManager().clearNonBondedDevices();
-            mInitialScanStarted = true;
+            setFilter(BluetoothDeviceFilter.ALL_FILTER);
+            updateFooterPreference(mFooterPreference);
+            mAlwaysDiscoverable.start();
         }
-        super.enableScanning();
-    }
-
-    @Override
-    void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
-        disableScanning();
-        super.onDevicePreferenceClick(btPreference);
     }
 
     @Override
@@ -146,78 +117,6 @@
         mAvailableDevicesCategory.setProgress(started);
     }
 
-    @VisibleForTesting
-    void updateContent(int bluetoothState) {
-        switch (bluetoothState) {
-            case BluetoothAdapter.STATE_ON:
-                mDevicePreferenceMap.clear();
-                mBluetoothAdapter.enable();
-
-                addDeviceCategory(mAvailableDevicesCategory,
-                        R.string.bluetooth_preference_found_media_devices,
-                        BluetoothDeviceFilter.ALL_FILTER, mInitialScanStarted);
-                updateFooterPreference(mFooterPreference);
-                mAlwaysDiscoverable.start();
-                enableScanning();
-                break;
-
-            case BluetoothAdapter.STATE_OFF:
-                finish();
-                break;
-        }
-    }
-
-    @Override
-    public void onBluetoothStateChanged(int bluetoothState) {
-        super.onBluetoothStateChanged(bluetoothState);
-        updateContent(bluetoothState);
-        if (bluetoothState == BluetoothAdapter.STATE_ON) {
-            showBluetoothTurnedOnToast();
-        }
-    }
-
-    @Override
-    public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
-        if (bondState == BluetoothDevice.BOND_BONDED) {
-            // If one device is connected(bonded), then close this fragment.
-            finish();
-            return;
-        } else if (bondState == BluetoothDevice.BOND_BONDING) {
-            // Set the bond entry where binding process starts for logging hearing aid device info
-            final int pageId = FeatureFactory.getFactory(
-                    getContext()).getMetricsFeatureProvider().getAttribution(getActivity());
-            final int bondEntry = AccessibilityStatsLogUtils.convertToHearingAidInfoBondEntry(
-                    pageId);
-            HearingAidStatsLogUtils.setBondEntryForDevice(bondEntry, cachedDevice);
-        }
-        if (mSelectedDevice != null && cachedDevice != null) {
-            BluetoothDevice device = cachedDevice.getDevice();
-            if (device != null && mSelectedDevice.equals(device)
-                    && bondState == BluetoothDevice.BOND_NONE) {
-                // If currently selected device failed to bond, restart scanning
-                enableScanning();
-            }
-        }
-    }
-
-    @Override
-    public void onProfileConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state,
-            int bluetoothProfile) {
-        // This callback is used to handle the case that bonded device is connected in pairing list.
-        // 1. If user selected multiple bonded devices in pairing list, after connected
-        // finish this page.
-        // 2. If the bonded devices auto connected in paring list, after connected it will be
-        // removed from paring list.
-        if (cachedDevice != null && cachedDevice.isConnected()) {
-            final BluetoothDevice device = cachedDevice.getDevice();
-            if (device != null && mSelectedList.contains(device)) {
-                finish();
-            } else if (mDevicePreferenceMap.containsKey(cachedDevice)) {
-                onDeviceDeleted(cachedDevice);
-            }
-        }
-    }
-
     @Override
     public int getHelpResource() {
         return R.string.help_url_bluetooth;
@@ -237,10 +136,4 @@
     public String getDeviceListKey() {
         return KEY_AVAIL_DEVICES;
     }
-
-    @VisibleForTesting
-    void showBluetoothTurnedOnToast() {
-        Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
-                Toast.LENGTH_SHORT).show();
-    }
 }
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
new file mode 100644
index 0000000..184f521
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.Pair;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Tests for {@link BluetoothDevicePairingDetailBase}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class BluetoothDevicePairingDetailBaseTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    public static final String KEY_DEVICE_LIST_GROUP = "test_key";
+
+    private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
+    private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1";
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    @Mock
+    private Resources mResource;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private LocalBluetoothManager mLocalManager;
+    @Mock
+    private CachedBluetoothDevice mCachedBluetoothDevice;
+    @Mock
+    private Drawable mDrawable;
+    private BluetoothAdapter mBluetoothAdapter;
+    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
+    private BluetoothProgressCategory mAvailableDevicesCategory;
+    private BluetoothDevice mBluetoothDevice;
+    private TestBluetoothDevicePairingDetailBase mFragment;
+
+    @Before
+    public void setUp() {
+        mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+        final Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
+        when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
+        mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+
+        mFragment = spy(new TestBluetoothDevicePairingDetailBase());
+        when(mFragment.findPreference(KEY_DEVICE_LIST_GROUP)).thenReturn(mAvailableDevicesCategory);
+        doReturn(mContext).when(mFragment).getContext();
+        doReturn(mResource).when(mFragment).getResources();
+        mFragment.mDeviceListGroup = mAvailableDevicesCategory;
+        mFragment.mLocalManager = mLocalManager;
+        mFragment.mBluetoothAdapter = mBluetoothAdapter;
+        mFragment.initPreferencesFromPreferenceScreen();
+
+    }
+
+    @Test
+    public void startScanning_startScanAndRemoveDevices() {
+        mFragment.enableScanning();
+
+        verify(mFragment).startScanning();
+        verify(mAvailableDevicesCategory).removeAll();
+    }
+
+    @Test
+    public void updateContent_stateOn() {
+        mFragment.updateContent(BluetoothAdapter.STATE_ON);
+
+        assertThat(mBluetoothAdapter.isEnabled()).isTrue();
+        verify(mFragment).enableScanning();
+    }
+
+    @Test
+    public void updateContent_stateOff_finish() {
+        mFragment.updateContent(BluetoothAdapter.STATE_OFF);
+
+        verify(mFragment).finish();
+    }
+
+    @Test
+    public void updateBluetooth_bluetoothOff_turnOnBluetooth() {
+        mShadowBluetoothAdapter.setEnabled(false);
+
+        mFragment.updateBluetooth();
+
+        assertThat(mBluetoothAdapter.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateBluetooth_bluetoothOn_updateState() {
+        mShadowBluetoothAdapter.setEnabled(true);
+        doNothing().when(mFragment).updateContent(anyInt());
+
+        mFragment.updateBluetooth();
+
+        verify(mFragment).updateContent(anyInt());
+    }
+
+    @Test
+    public void onBluetoothStateChanged_whenTurnedOnBTShowToast() {
+        doNothing().when(mFragment).updateContent(anyInt());
+
+        mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
+
+        verify(mFragment).showBluetoothTurnedOnToast();
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
+        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        mFragment.mSelectedList.add(device);
+
+        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
+
+        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+
+        verify(mFragment).finish();
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
+        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
+        mFragment.mSelectedList.add(device);
+
+        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+
+        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+
+        // not crash
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
+        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
+        mFragment.mSelectedList.add(mBluetoothDevice);
+        mFragment.mSelectedList.add(device);
+
+        when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
+
+        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED);
+
+        // not crash
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
+        final BluetoothDevicePreference preference =
+                new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
+                        true, BluetoothDevicePreference.SortType.TYPE_FIFO);
+        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+        mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
+
+        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
+
+        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
+                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
+
+        assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
+        final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
+        final BluetoothDevicePreference preference =
+                new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
+                        true, BluetoothDevicePreference.SortType.TYPE_FIFO);
+        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+        final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
+        mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
+
+        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
+        when(cachedDevice.isConnected()).thenReturn(true);
+        when(cachedDevice.getDevice()).thenReturn(device2);
+        when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
+        when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
+
+        mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP,
+                BluetoothAdapter.STATE_CONNECTED);
+
+        // not crash
+    }
+
+    private static class TestBluetoothDevicePairingDetailBase extends
+            BluetoothDevicePairingDetailBase {
+
+        TestBluetoothDevicePairingDetailBase() {
+            super();
+        }
+
+        @Override
+        public int getMetricsCategory() {
+            return 0;
+        }
+
+        @Override
+        public String getDeviceListKey() {
+            return KEY_DEVICE_LIST_GROUP;
+        }
+
+        @Override
+        protected int getPreferenceScreenResId() {
+            return 0;
+        }
+
+        @Override
+        protected String getLogTag() {
+            return "test_tag";
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
index bac868f..865ed81 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
@@ -18,102 +18,66 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.util.Pair;
+import android.os.Bundle;
 
-import androidx.preference.PreferenceGroup;
+import androidx.test.core.app.ApplicationProvider;
 
-import com.android.settings.R;
-import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
-import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.widget.FooterPreference;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.shadow.api.Shadow;
 
 @RunWith(RobolectricTestRunner.class)
-@Config(shadows = {ShadowBluetoothAdapter.class})
 public class BluetoothPairingDetailTest {
-    private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
-    private static final String TEST_DEVICE_ADDRESS_B = "00:B1:B1:B1:B1:B1";
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
 
-    @Mock
-    private Resources mResource;
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private LocalBluetoothManager mLocalManager;
-    @Mock
-    private PreferenceGroup mPreferenceGroup;
-    @Mock
-    private CachedBluetoothDevice mCachedBluetoothDevice;
-    @Mock
-    private Drawable mDrawable;
-
     private BluetoothPairingDetail mFragment;
-    private Context mContext;
     private BluetoothProgressCategory mAvailableDevicesCategory;
     private FooterPreference mFooterPreference;
     private BluetoothAdapter mBluetoothAdapter;
-    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
-    private BluetoothDevice mBluetoothDevice;
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        Pair<Drawable, String> pairs = new Pair<>(mDrawable, "fake_device");
-        mContext = RuntimeEnvironment.application;
         mFragment = spy(new BluetoothPairingDetail());
         doReturn(mContext).when(mFragment).getContext();
-        doReturn(mResource).when(mFragment).getResources();
-        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
-        when(mCachedBluetoothDevice.getDrawableWithDescription()).thenReturn(pairs);
-
         mAvailableDevicesCategory = spy(new BluetoothProgressCategory(mContext));
         mFooterPreference = new FooterPreference(mContext);
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
-        mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
+
+        doReturn(mAvailableDevicesCategory).when(mFragment)
+                .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
+        doReturn(mFooterPreference).when(mFragment)
+                .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
 
         mFragment.mBluetoothAdapter = mBluetoothAdapter;
         mFragment.mLocalManager = mLocalManager;
-        mFragment.mDeviceListGroup = mPreferenceGroup;
-        mFragment.mAlwaysDiscoverable = new AlwaysDiscoverable(mContext);
+        mFragment.mDeviceListGroup = mAvailableDevicesCategory;
+        mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY);
     }
-
+//
     @Test
     public void initPreferencesFromPreferenceScreen_findPreferences() {
-        doReturn(mAvailableDevicesCategory).when(mFragment)
-            .findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
-        doReturn(mFooterPreference).when(mFragment)
-            .findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
-
         mFragment.initPreferencesFromPreferenceScreen();
 
         assertThat(mFragment.mAvailableDevicesCategory).isEqualTo(mAvailableDevicesCategory);
@@ -121,63 +85,19 @@
     }
 
     @Test
-    public void startScanning_startScanAndRemoveDevices() {
-        mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory;
-        mFragment.mDeviceListGroup = mAvailableDevicesCategory;
-
-        mFragment.enableScanning();
-
-        verify(mFragment).startScanning();
-        verify(mAvailableDevicesCategory).removeAll();
-    }
-
-    @Test
     public void updateContent_stateOn_addDevices() {
-        mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory;
-        mFragment.mFooterPreference = mFooterPreference;
-        doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean());
+        mFragment.initPreferencesFromPreferenceScreen();
 
         mFragment.updateContent(BluetoothAdapter.STATE_ON);
 
-        verify(mFragment).addDeviceCategory(mAvailableDevicesCategory,
-                R.string.bluetooth_preference_found_media_devices,
-                BluetoothDeviceFilter.ALL_FILTER, false);
+        assertThat(mFragment.mAlwaysDiscoverable.mStarted).isEqualTo(true);
         assertThat(mBluetoothAdapter.getScanMode())
                 .isEqualTo(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
     }
 
     @Test
-    public void updateContent_stateOff_finish() {
-        mFragment.updateContent(BluetoothAdapter.STATE_OFF);
-
-        verify(mFragment).finish();
-    }
-
-    @Test
-    public void updateBluetooth_bluetoothOff_turnOnBluetooth() {
-        mShadowBluetoothAdapter.setEnabled(false);
-
-        mFragment.updateBluetooth();
-
-        assertThat(mBluetoothAdapter.isEnabled()).isTrue();
-    }
-
-    @Test
-    public void updateBluetooth_bluetoothOn_updateState() {
-        mShadowBluetoothAdapter.setEnabled(true);
-        doNothing().when(mFragment).updateContent(anyInt());
-
-        mFragment.updateBluetooth();
-
-        verify(mFragment).updateContent(anyInt());
-    }
-
-    @Test
     public void onScanningStateChanged_restartScanAfterInitialScanning() {
-        mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory;
-        mFragment.mFooterPreference = mFooterPreference;
-        mFragment.mDeviceListGroup = mAvailableDevicesCategory;
-        doNothing().when(mFragment).addDeviceCategory(any(), anyInt(), any(), anyBoolean());
+        mFragment.initPreferencesFromPreferenceScreen();
 
         // Initial Bluetooth ON will trigger scan enable, list clear and scan start
         mFragment.updateContent(BluetoothAdapter.STATE_ON);
@@ -219,97 +139,4 @@
         // Verify that clean up only happen once at initialization
         verify(mAvailableDevicesCategory, times(1)).removeAll();
     }
-
-    @Test
-    public void onBluetoothStateChanged_whenTurnedOnBTShowToast() {
-        doNothing().when(mFragment).updateContent(anyInt());
-
-        mFragment.onBluetoothStateChanged(BluetoothAdapter.STATE_ON);
-
-        verify(mFragment).showBluetoothTurnedOnToast();
-    }
-
-    @Test
-    public void onProfileConnectionStateChanged_deviceInSelectedListAndConnected_finish() {
-        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
-        mFragment.mSelectedList.add(mBluetoothDevice);
-        mFragment.mSelectedList.add(device);
-
-        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
-
-        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
-
-        verify(mFragment).finish();
-    }
-
-    @Test
-    public void onProfileConnectionStateChanged_deviceNotInSelectedList_doNothing() {
-        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
-        mFragment.mSelectedList.add(device);
-
-        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
-
-        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
-
-        // not crash
-    }
-
-    @Test
-    public void onProfileConnectionStateChanged_deviceDisconnected_doNothing() {
-        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
-        mFragment.mSelectedList.add(mBluetoothDevice);
-        mFragment.mSelectedList.add(device);
-
-        when(mCachedBluetoothDevice.isConnected()).thenReturn(false);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
-
-        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_DISCONNECTED);
-
-        // not crash
-    }
-
-    @Test
-    public void onProfileConnectionStateChanged_deviceInPreferenceMapAndConnected_removed() {
-        final BluetoothDevicePreference preference =
-                new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
-                true, BluetoothDevicePreference.SortType.TYPE_FIFO);
-        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
-        mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
-
-        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
-
-        mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
-                BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
-
-        assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
-    }
-
-    @Test
-    public void onProfileConnectionStateChanged_deviceNotInPreferenceMap_doNothing() {
-        final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
-        final BluetoothDevicePreference preference =
-                new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
-                        true, BluetoothDevicePreference.SortType.TYPE_FIFO);
-        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
-        final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
-        mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
-
-        when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
-        when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
-        when(cachedDevice.isConnected()).thenReturn(true);
-        when(cachedDevice.getDevice()).thenReturn(device2);
-        when(cachedDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
-        when(cachedDevice.getIdentityAddress()).thenReturn(TEST_DEVICE_ADDRESS_B);
-
-        mFragment.onProfileConnectionStateChanged(cachedDevice, BluetoothProfile.A2DP,
-                BluetoothAdapter.STATE_CONNECTED);
-
-        // not crash
-    }
 }
\ No newline at end of file