Merge "Fix bluetooth settings pairing page stuck" into udc-qpr-dev am: a1fd330fb5
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Settings/+/23657645
Change-Id: I7bb7e5fd8e647e2f99532ef56effcf9b1f10587d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
index 7ee61ee..f2bc6fc 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBase.java
@@ -128,7 +128,7 @@
if (device != null && mSelectedList.contains(device)) {
setResult(RESULT_OK);
finish();
- } else if (mDevicePreferenceMap.containsKey(cachedDevice)) {
+ } else {
onDeviceDeleted(cachedDevice);
}
}
@@ -175,8 +175,6 @@
public void updateContent(int bluetoothState) {
switch (bluetoothState) {
case BluetoothAdapter.STATE_ON:
- mDevicePreferenceMap.clear();
- clearPreferenceGroupCache();
mBluetoothAdapter.enable();
enableScanning();
break;
@@ -187,14 +185,6 @@
}
}
- /**
- * 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,
diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
index 5256f3d..039080b 100644
--- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
+++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * 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.
@@ -35,6 +35,8 @@
import android.widget.ImageView;
import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
@@ -52,6 +54,7 @@
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* BluetoothDevicePreference is the preference type used to display each remote
@@ -79,7 +82,9 @@
@VisibleForTesting
BluetoothAdapter mBluetoothAdapter;
private final boolean mShowDevicesWithoutNames;
- private final long mCurrentTime;
+ @NonNull
+ private static final AtomicInteger sNextId = new AtomicInteger();
+ private final int mId;
private final int mType;
private AlertDialog mDisconnectDialog;
@@ -127,8 +132,9 @@
mCachedDevice = cachedDevice;
mCallback = new BluetoothDevicePreferenceCallback();
- mCurrentTime = System.currentTimeMillis();
+ mId = sNextId.getAndIncrement();
mType = type;
+ setVisible(false);
onPreferenceAttributesChanged();
}
@@ -229,35 +235,41 @@
@SuppressWarnings("FutureReturnValueIgnored")
void onPreferenceAttributesChanged() {
- Pair<Drawable, String> pair = mCachedDevice.getDrawableWithDescription();
- setIcon(pair.first);
- contentDescription = pair.second;
-
- /*
- * The preference framework takes care of making sure the value has
- * changed before proceeding. It will also call notifyChanged() if
- * any preference info has changed from the previous value.
- */
- setTitle(mCachedDevice.getName());
try {
ThreadUtils.postOnBackgroundThread(() -> {
+ @Nullable String name = mCachedDevice.getName();
// Null check is done at the framework
- ThreadUtils.postOnMainThread(() -> setSummary(getConnectionSummary()));
+ @Nullable String connectionSummary = getConnectionSummary();
+ @NonNull Pair<Drawable, String> pair = mCachedDevice.getDrawableWithDescription();
+ boolean isBusy = mCachedDevice.isBusy();
+ // Device is only visible in the UI if it has a valid name besides MAC address or
+ // when user allows showing devices without user-friendly name in developer settings
+ boolean isVisible =
+ mShowDevicesWithoutNames || mCachedDevice.hasHumanReadableName();
+
+ ThreadUtils.postOnMainThread(() -> {
+ /*
+ * The preference framework takes care of making sure the value has
+ * changed before proceeding. It will also call notifyChanged() if
+ * any preference info has changed from the previous value.
+ */
+ setTitle(name);
+ setSummary(connectionSummary);
+ setIcon(pair.first);
+ contentDescription = pair.second;
+ // Used to gray out the item
+ setEnabled(!isBusy);
+ setVisible(isVisible);
+
+ // This could affect ordering, so notify that
+ if (mNeedNotifyHierarchyChanged) {
+ notifyHierarchyChanged();
+ }
+ });
});
} catch (RejectedExecutionException e) {
Log.w(TAG, "Handler thread unavailable, skipping getConnectionSummary!");
}
- // Used to gray out the item
- setEnabled(!mCachedDevice.isBusy());
-
- // Device is only visible in the UI if it has a valid name besides MAC address or when user
- // allows showing devices without user-friendly name in developer settings
- setVisible(mShowDevicesWithoutNames || mCachedDevice.hasHumanReadableName());
-
- // This could affect ordering, so notify that
- if (mNeedNotifyHierarchyChanged) {
- notifyHierarchyChanged();
- }
}
@Override
@@ -311,7 +323,7 @@
return mCachedDevice
.compareTo(((BluetoothDevicePreference) another).mCachedDevice);
case SortType.TYPE_FIFO:
- return mCurrentTime > ((BluetoothDevicePreference) another).mCurrentTime ? 1 : -1;
+ return mId > ((BluetoothDevicePreference) another).mId ? 1 : -1;
default:
return super.compareTo(another);
}
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
deleted file mode 100644
index a4a9891..0000000
--- a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * Copyright (C) 2011 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 android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.os.Bundle;
-import android.os.SystemProperties;
-import android.text.BidiFormatter;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceGroup;
-
-import com.android.settings.R;
-import com.android.settings.dashboard.RestrictedDashboardFragment;
-import com.android.settingslib.bluetooth.BluetoothCallback;
-import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Parent class for settings fragments that contain a list of Bluetooth
- * devices.
- *
- * @see DevicePickerFragment
- */
-// TODO: Refactor this fragment
-public abstract class DeviceListPreferenceFragment extends
- RestrictedDashboardFragment implements BluetoothCallback {
-
- private static final String TAG = "DeviceListPreferenceFragment";
-
- private static final String KEY_BT_SCAN = "bt_scan";
-
- // Copied from BluetoothDeviceNoNamePreferenceController.java
- private static final String BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
- "persist.bluetooth.showdeviceswithoutnames";
-
- private BluetoothDeviceFilter.Filter mFilter;
- private List<ScanFilter> mLeScanFilters;
- private ScanCallback mScanCallback;
-
- @VisibleForTesting
- protected boolean mScanEnabled;
-
- protected BluetoothDevice mSelectedDevice;
-
- protected BluetoothAdapter mBluetoothAdapter;
- protected LocalBluetoothManager mLocalManager;
- protected CachedBluetoothDeviceManager mCachedDeviceManager;
-
- @VisibleForTesting
- protected PreferenceGroup mDeviceListGroup;
-
- protected final HashMap<CachedBluetoothDevice, BluetoothDevicePreference> mDevicePreferenceMap =
- new HashMap<>();
- protected final List<BluetoothDevice> mSelectedList = new ArrayList<>();
-
- protected boolean mShowDevicesWithoutNames;
-
- public DeviceListPreferenceFragment(String restrictedKey) {
- super(restrictedKey);
- mFilter = BluetoothDeviceFilter.ALL_FILTER;
- }
-
- protected final void setFilter(BluetoothDeviceFilter.Filter filter) {
- mFilter = filter;
- }
-
- protected final void setFilter(int filterType) {
- mFilter = BluetoothDeviceFilter.getFilter(filterType);
- }
-
- /**
- * Sets the bluetooth device scanning filter with {@link ScanFilter}s. It will change to start
- * {@link BluetoothLeScanner} which will scan BLE device only.
- *
- * @param leScanFilters list of settings to filter scan result
- */
- protected void setFilter(List<ScanFilter> leScanFilters) {
- mFilter = null;
- mLeScanFilters = leScanFilters;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- mLocalManager = Utils.getLocalBtManager(getActivity());
- if (mLocalManager == null) {
- Log.e(TAG, "Bluetooth is not supported on this device");
- return;
- }
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mCachedDeviceManager = mLocalManager.getCachedDeviceManager();
- mShowDevicesWithoutNames = SystemProperties.getBoolean(
- BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false);
-
- initPreferencesFromPreferenceScreen();
-
- mDeviceListGroup = (PreferenceCategory) findPreference(getDeviceListKey());
- }
-
- /** find and update preference that already existed in preference screen */
- protected abstract void initPreferencesFromPreferenceScreen();
-
- @Override
- public void onStart() {
- super.onStart();
- if (mLocalManager == null || isUiRestricted()) return;
-
- mLocalManager.setForegroundActivity(getActivity());
- mLocalManager.getEventManager().registerCallback(this);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (mLocalManager == null || isUiRestricted()) {
- return;
- }
-
- removeAllDevices();
- mLocalManager.setForegroundActivity(null);
- mLocalManager.getEventManager().unregisterCallback(this);
- }
-
- void removeAllDevices() {
- mDevicePreferenceMap.clear();
- mDeviceListGroup.removeAll();
- }
-
- void addCachedDevices() {
- Collection<CachedBluetoothDevice> cachedDevices =
- mCachedDeviceManager.getCachedDevicesCopy();
- for (CachedBluetoothDevice cachedDevice : cachedDevices) {
- onDeviceAdded(cachedDevice);
- }
- }
-
- @Override
- public boolean onPreferenceTreeClick(Preference preference) {
- if (KEY_BT_SCAN.equals(preference.getKey())) {
- startScanning();
- return true;
- }
-
- if (preference instanceof BluetoothDevicePreference) {
- BluetoothDevicePreference btPreference = (BluetoothDevicePreference) preference;
- CachedBluetoothDevice device = btPreference.getCachedDevice();
- mSelectedDevice = device.getDevice();
- mSelectedList.add(mSelectedDevice);
- onDevicePreferenceClick(btPreference);
- return true;
- }
-
- return super.onPreferenceTreeClick(preference);
- }
-
- protected void onDevicePreferenceClick(BluetoothDevicePreference btPreference) {
- btPreference.onClicked();
- }
-
- @Override
- public void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
- if (mDevicePreferenceMap.get(cachedDevice) != null) {
- return;
- }
-
- // Prevent updates while the list shows one of the state messages
- if (mBluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) {
- return;
- }
-
- if (mFilter != null && mFilter.matches(cachedDevice.getDevice())) {
- createDevicePreference(cachedDevice);
- }
- }
-
- void createDevicePreference(CachedBluetoothDevice cachedDevice) {
- if (mDeviceListGroup == null) {
- Log.w(TAG, "Trying to create a device preference before the list group/category "
- + "exists!");
- return;
- }
-
- String key = cachedDevice.getDevice().getAddress();
- BluetoothDevicePreference preference = (BluetoothDevicePreference) getCachedPreference(key);
-
- if (preference == null) {
- preference = new BluetoothDevicePreference(getPrefContext(), cachedDevice,
- mShowDevicesWithoutNames, BluetoothDevicePreference.SortType.TYPE_FIFO);
- preference.setKey(key);
- //Set hideSecondTarget is true if it's bonded device.
- preference.hideSecondTarget(true);
- mDeviceListGroup.addPreference(preference);
- }
-
- initDevicePreference(preference);
- mDevicePreferenceMap.put(cachedDevice, preference);
- }
-
- protected void initDevicePreference(BluetoothDevicePreference preference) {
- // Does nothing by default
- }
-
- @VisibleForTesting
- void updateFooterPreference(Preference myDevicePreference) {
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
-
- myDevicePreference.setTitle(getString(
- R.string.bluetooth_footer_mac_message,
- bidiFormatter.unicodeWrap(mBluetoothAdapter.getAddress())));
- }
-
- @Override
- public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) {
- BluetoothDevicePreference preference = mDevicePreferenceMap.remove(cachedDevice);
- if (preference != null) {
- mDeviceListGroup.removePreference(preference);
- }
- }
-
- @VisibleForTesting
- protected void enableScanning() {
- // BluetoothAdapter already handles repeated scan requests
- if (!mScanEnabled) {
- startScanning();
- mScanEnabled = true;
- }
- }
-
- @VisibleForTesting
- protected void disableScanning() {
- if (mScanEnabled) {
- stopScanning();
- mScanEnabled = false;
- }
- }
-
- @Override
- public void onScanningStateChanged(boolean started) {
- if (!started && mScanEnabled) {
- startScanning();
- }
- }
-
- /**
- * Return the key of the {@link PreferenceGroup} that contains the bluetooth devices
- */
- public abstract String getDeviceListKey();
-
- public boolean shouldShowDevicesWithoutNames() {
- return mShowDevicesWithoutNames;
- }
-
- @VisibleForTesting
- void startScanning() {
- if (mFilter != null) {
- startClassicScanning();
- } else if (mLeScanFilters != null) {
- startLeScanning();
- }
-
- }
-
- @VisibleForTesting
- void stopScanning() {
- if (mFilter != null) {
- stopClassicScanning();
- } else if (mLeScanFilters != null) {
- stopLeScanning();
- }
- }
-
- private void startClassicScanning() {
- if (!mBluetoothAdapter.isDiscovering()) {
- mBluetoothAdapter.startDiscovery();
- }
- }
-
- private void stopClassicScanning() {
- if (mBluetoothAdapter.isDiscovering()) {
- mBluetoothAdapter.cancelDiscovery();
- }
- }
-
- private void startLeScanning() {
- final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
- final ScanSettings settings = new ScanSettings.Builder()
- .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
- .build();
- mScanCallback = new ScanCallback() {
- @Override
- public void onScanResult(int callbackType, ScanResult result) {
- final BluetoothDevice device = result.getDevice();
- CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(device);
- if (cachedDevice == null) {
- cachedDevice = mCachedDeviceManager.addDevice(device);
- }
- // Only add device preference when it's not found in the map and there's no other
- // state message showing in the list
- if (mDevicePreferenceMap.get(cachedDevice) == null
- && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {
- createDevicePreference(cachedDevice);
- }
- }
-
- @Override
- public void onScanFailed(int errorCode) {
- Log.w(TAG, "BLE Scan failed with error code " + errorCode);
- }
- };
- scanner.startScan(mLeScanFilters, settings, mScanCallback);
- }
-
- private void stopLeScanning() {
- final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();
- if (scanner != null) {
- scanner.stopScan(mScanCallback);
- }
- }
-}
diff --git a/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
new file mode 100644
index 0000000..9c86e43
--- /dev/null
+++ b/src/com/android/settings/bluetooth/DeviceListPreferenceFragment.kt
@@ -0,0 +1,348 @@
+/*
+ * 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 android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.le.BluetoothLeScanner
+import android.bluetooth.le.ScanCallback
+import android.bluetooth.le.ScanFilter
+import android.bluetooth.le.ScanResult
+import android.bluetooth.le.ScanSettings
+import android.os.Bundle
+import android.os.SystemProperties
+import android.text.BidiFormatter
+import android.util.Log
+import android.view.View
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.LifecycleCoroutineScope
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceGroup
+import com.android.settings.R
+import com.android.settings.dashboard.RestrictedDashboardFragment
+import com.android.settingslib.bluetooth.BluetoothCallback
+import com.android.settingslib.bluetooth.BluetoothDeviceFilter
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import java.util.concurrent.ConcurrentHashMap
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Parent class for settings fragments that contain a list of Bluetooth devices.
+ *
+ * @see DevicePickerFragment
+ *
+ * TODO: Refactor this fragment
+ */
+abstract class DeviceListPreferenceFragment(restrictedKey: String?) :
+ RestrictedDashboardFragment(restrictedKey), BluetoothCallback {
+
+ private var filter: BluetoothDeviceFilter.Filter? = BluetoothDeviceFilter.ALL_FILTER
+ private var leScanFilters: List<ScanFilter>? = null
+
+ @JvmField
+ @VisibleForTesting
+ var mScanEnabled = false
+
+ @JvmField
+ var mSelectedDevice: BluetoothDevice? = null
+
+ @JvmField
+ var mBluetoothAdapter: BluetoothAdapter? = null
+
+ @JvmField
+ var mLocalManager: LocalBluetoothManager? = null
+
+ @JvmField
+ var mCachedDeviceManager: CachedBluetoothDeviceManager? = null
+
+ @JvmField
+ @VisibleForTesting
+ var mDeviceListGroup: PreferenceGroup? = null
+
+ @VisibleForTesting
+ val devicePreferenceMap =
+ ConcurrentHashMap<CachedBluetoothDevice, BluetoothDevicePreference>()
+
+ @JvmField
+ val mSelectedList: MutableList<BluetoothDevice> = ArrayList()
+
+ private var showDevicesWithoutNames = false
+
+ protected fun setFilter(filter: BluetoothDeviceFilter.Filter?) {
+ this.filter = filter
+ }
+
+ protected fun setFilter(filterType: Int) {
+ filter = BluetoothDeviceFilter.getFilter(filterType)
+ }
+
+ /**
+ * Sets the bluetooth device scanning filter with [ScanFilter]s. It will change to start
+ * [BluetoothLeScanner] which will scan BLE device only.
+ *
+ * @param leScanFilters list of settings to filter scan result
+ */
+ fun setFilter(leScanFilters: List<ScanFilter>?) {
+ filter = null
+ this.leScanFilters = leScanFilters
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ mLocalManager = Utils.getLocalBtManager(activity)
+ if (mLocalManager == null) {
+ Log.e(TAG, "Bluetooth is not supported on this device")
+ return
+ }
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
+ mCachedDeviceManager = mLocalManager!!.cachedDeviceManager
+ showDevicesWithoutNames = SystemProperties.getBoolean(
+ BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY, false
+ )
+ initPreferencesFromPreferenceScreen()
+ mDeviceListGroup = findPreference<Preference>(deviceListKey) as PreferenceCategory
+ }
+
+ /** find and update preference that already existed in preference screen */
+ protected abstract fun initPreferencesFromPreferenceScreen()
+
+ private var lifecycleScope: LifecycleCoroutineScope? = null
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ lifecycleScope = viewLifecycleOwner.lifecycleScope
+ }
+
+ override fun onStart() {
+ super.onStart()
+ if (mLocalManager == null || isUiRestricted) return
+ mLocalManager!!.foregroundActivity = activity
+ mLocalManager!!.eventManager.registerCallback(this)
+ }
+
+ override fun onStop() {
+ super.onStop()
+ if (mLocalManager == null || isUiRestricted) {
+ return
+ }
+ removeAllDevices()
+ mLocalManager!!.foregroundActivity = null
+ mLocalManager!!.eventManager.unregisterCallback(this)
+ }
+
+ fun removeAllDevices() {
+ devicePreferenceMap.clear()
+ mDeviceListGroup!!.removeAll()
+ }
+
+ fun addCachedDevices() {
+ lifecycleScope?.launch {
+ withContext(Dispatchers.Default) {
+ val cachedDevices = mCachedDeviceManager!!.cachedDevicesCopy
+ for (cachedDevice in cachedDevices) {
+ onDeviceAdded(cachedDevice)
+ }
+ }
+ }
+ }
+
+ override fun onPreferenceTreeClick(preference: Preference): Boolean {
+ if (KEY_BT_SCAN == preference.key) {
+ startScanning()
+ return true
+ }
+ if (preference is BluetoothDevicePreference) {
+ val device = preference.cachedDevice.device
+ mSelectedDevice = device
+ mSelectedList.add(device)
+ onDevicePreferenceClick(preference)
+ return true
+ }
+ return super.onPreferenceTreeClick(preference)
+ }
+
+ protected open fun onDevicePreferenceClick(btPreference: BluetoothDevicePreference) {
+ btPreference.onClicked()
+ }
+
+ override fun onDeviceAdded(cachedDevice: CachedBluetoothDevice) {
+ lifecycleScope?.launch {
+ addDevice(cachedDevice)
+ }
+ }
+
+ private suspend fun addDevice(cachedDevice: CachedBluetoothDevice) =
+ withContext(Dispatchers.Default) {
+ // Prevent updates while the list shows one of the state messages
+ if (mBluetoothAdapter!!.state == BluetoothAdapter.STATE_ON &&
+ filter?.matches(cachedDevice.device) == true
+ ) {
+ createDevicePreference(cachedDevice)
+ }
+ }
+
+ private suspend fun createDevicePreference(cachedDevice: CachedBluetoothDevice) {
+ if (mDeviceListGroup == null) {
+ Log.w(
+ TAG,
+ "Trying to create a device preference before the list group/category exists!",
+ )
+ return
+ }
+ // Only add device preference when it's not found in the map and there's no other state
+ // message showing in the list
+ val preference = devicePreferenceMap.computeIfAbsent(cachedDevice) {
+ BluetoothDevicePreference(
+ prefContext,
+ cachedDevice,
+ showDevicesWithoutNames,
+ BluetoothDevicePreference.SortType.TYPE_FIFO,
+ ).apply {
+ key = cachedDevice.device.address
+ //Set hideSecondTarget is true if it's bonded device.
+ hideSecondTarget(true)
+ }
+ }
+ withContext(Dispatchers.Main) {
+ mDeviceListGroup!!.addPreference(preference)
+ initDevicePreference(preference)
+ }
+ }
+
+ protected open fun initDevicePreference(preference: BluetoothDevicePreference?) {
+ // Does nothing by default
+ }
+
+ @VisibleForTesting
+ fun updateFooterPreference(myDevicePreference: Preference) {
+ val bidiFormatter = BidiFormatter.getInstance()
+ myDevicePreference.title = getString(
+ R.string.bluetooth_footer_mac_message,
+ bidiFormatter.unicodeWrap(mBluetoothAdapter!!.address)
+ )
+ }
+
+ override fun onDeviceDeleted(cachedDevice: CachedBluetoothDevice) {
+ devicePreferenceMap.remove(cachedDevice)?.let {
+ mDeviceListGroup!!.removePreference(it)
+ }
+ }
+
+ @VisibleForTesting
+ open fun enableScanning() {
+ // BluetoothAdapter already handles repeated scan requests
+ if (!mScanEnabled) {
+ startScanning()
+ mScanEnabled = true
+ }
+ }
+
+ @VisibleForTesting
+ fun disableScanning() {
+ if (mScanEnabled) {
+ stopScanning()
+ mScanEnabled = false
+ }
+ }
+
+ override fun onScanningStateChanged(started: Boolean) {
+ if (!started && mScanEnabled) {
+ startScanning()
+ }
+ }
+
+ /**
+ * Return the key of the [PreferenceGroup] that contains the bluetooth devices
+ */
+ abstract val deviceListKey: String
+
+ @VisibleForTesting
+ open fun startScanning() {
+ if (filter != null) {
+ startClassicScanning()
+ } else if (leScanFilters != null) {
+ startLeScanning()
+ }
+ }
+
+ @VisibleForTesting
+ open fun stopScanning() {
+ if (filter != null) {
+ stopClassicScanning()
+ } else if (leScanFilters != null) {
+ stopLeScanning()
+ }
+ }
+
+ private fun startClassicScanning() {
+ if (!mBluetoothAdapter!!.isDiscovering) {
+ mBluetoothAdapter!!.startDiscovery()
+ }
+ }
+
+ private fun stopClassicScanning() {
+ if (mBluetoothAdapter!!.isDiscovering) {
+ mBluetoothAdapter!!.cancelDiscovery()
+ }
+ }
+
+ private val scanCallback = object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
+ lifecycleScope?.launch {
+ withContext(Dispatchers.Default) {
+ if (mBluetoothAdapter!!.state == BluetoothAdapter.STATE_ON) {
+ val device = result.device
+ val cachedDevice = mCachedDeviceManager!!.findDevice(device)
+ ?: mCachedDeviceManager!!.addDevice(device)
+ createDevicePreference(cachedDevice)
+ }
+ }
+ }
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ Log.w(TAG, "BLE Scan failed with error code $errorCode")
+ }
+ }
+
+ private fun startLeScanning() {
+ val scanner = mBluetoothAdapter!!.bluetoothLeScanner
+ val settings = ScanSettings.Builder()
+ .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+ .build()
+ scanner.startScan(leScanFilters, settings, scanCallback)
+ }
+
+ private fun stopLeScanning() {
+ val scanner = mBluetoothAdapter!!.bluetoothLeScanner
+ scanner?.stopScan(scanCallback)
+ }
+
+ companion object {
+ private const val TAG = "DeviceListPreferenceFragment"
+ private const val KEY_BT_SCAN = "bt_scan"
+
+ // Copied from BluetoothDeviceNoNamePreferenceController.java
+ private const val BLUETOOTH_SHOW_DEVICES_WITHOUT_NAMES_PROPERTY =
+ "persist.bluetooth.showdeviceswithoutnames"
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
index 184f521..7c598e0 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePairingDetailBaseTest.java
@@ -202,7 +202,7 @@
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
- mFragment.mDevicePreferenceMap.put(mCachedBluetoothDevice, preference);
+ mFragment.getDevicePreferenceMap().put(mCachedBluetoothDevice, preference);
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
@@ -210,7 +210,7 @@
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
- assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
+ assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
}
@Test
@@ -221,7 +221,7 @@
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);
+ mFragment.getDevicePreferenceMap().put(mCachedBluetoothDevice, preference);
when(mCachedBluetoothDevice.isConnected()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
index 5fbfee8..ce67051 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPairingDetailTest.java
@@ -27,7 +27,12 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.os.Bundle;
+import android.view.View;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.test.core.app.ApplicationProvider;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -53,6 +58,20 @@
private final Context mContext = ApplicationProvider.getApplicationContext();
+ private final Lifecycle mFakeLifecycle = new Lifecycle() {
+ @Override
+ public void addObserver(@NonNull LifecycleObserver observer) {}
+
+ @Override
+ public void removeObserver(@NonNull LifecycleObserver observer) {}
+
+ @NonNull
+ @Override
+ public State getCurrentState() {
+ return State.CREATED;
+ }
+ };
+
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private LocalBluetoothManager mLocalManager;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -74,6 +93,8 @@
.findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
doReturn(mFooterPreference).when(mFragment)
.findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
+ doReturn(new View(mContext)).when(mFragment).getView();
+ doReturn((LifecycleOwner) () -> mFakeLifecycle).when(mFragment).getViewLifecycleOwner();
doReturn(Collections.emptyList()).when(mDeviceManager).getCachedDevicesCopy();
mFragment.mBluetoothAdapter = mBluetoothAdapter;
@@ -82,7 +103,7 @@
mFragment.mDeviceListGroup = mAvailableDevicesCategory;
mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY);
}
-//
+
@Test
public void initPreferencesFromPreferenceScreen_findPreferences() {
mFragment.initPreferencesFromPreferenceScreen();