Fix bluetooth settings pairing page stuck
There is heavy work to do when add device to list in the DeviceListPreferenceFragment, off load these work from main thread to solve the issue. Make devicePreferenceMap a ConcurrentHashMap to avoid potential race condition. Also no longer use getCachedPreference(key) since we not put anything into the cache, the fallback flow is always used. Also in BluetoothDevicePreference.onPreferenceAttributesChanged(), move more heavy work to background thread. Using System.currentTimeMillis() to sort devices could cause flaky because System.currentTimeMillis() could be same for different device, use AtomicInteger instead. Fix: 286628533 Test: Following the step in bug Change-Id: Ia9750adb6b4c1424d084381e9d7c2ca8e7912391
This commit is contained in:
@@ -128,7 +128,7 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
if (device != null && mSelectedList.contains(device)) {
|
if (device != null && mSelectedList.contains(device)) {
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
finish();
|
finish();
|
||||||
} else if (mDevicePreferenceMap.containsKey(cachedDevice)) {
|
} else {
|
||||||
onDeviceDeleted(cachedDevice);
|
onDeviceDeleted(cachedDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,8 +175,6 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
public void updateContent(int bluetoothState) {
|
public void updateContent(int bluetoothState) {
|
||||||
switch (bluetoothState) {
|
switch (bluetoothState) {
|
||||||
case BluetoothAdapter.STATE_ON:
|
case BluetoothAdapter.STATE_ON:
|
||||||
mDevicePreferenceMap.clear();
|
|
||||||
clearPreferenceGroupCache();
|
|
||||||
mBluetoothAdapter.enable();
|
mBluetoothAdapter.enable();
|
||||||
enableScanning();
|
enableScanning();
|
||||||
break;
|
break;
|
||||||
@@ -187,14 +185,6 @@ public abstract class BluetoothDevicePairingDetailBase extends DeviceListPrefere
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears all cached preferences in {@code preferenceGroup}.
|
|
||||||
*/
|
|
||||||
private void clearPreferenceGroupCache() {
|
|
||||||
cacheRemoveAllPrefs(mAvailableDevicesCategory);
|
|
||||||
removeCachedPrefs(mAvailableDevicesCategory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void showBluetoothTurnedOnToast() {
|
void showBluetoothTurnedOnToast() {
|
||||||
Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
|
Toast.makeText(getContext(), R.string.connected_device_bluetooth_turned_on_toast,
|
||||||
|
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -35,6 +35,8 @@ import android.view.View;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
@@ -52,6 +54,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BluetoothDevicePreference is the preference type used to display each remote
|
* BluetoothDevicePreference is the preference type used to display each remote
|
||||||
@@ -79,7 +82,9 @@ public final class BluetoothDevicePreference extends GearPreference {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
BluetoothAdapter mBluetoothAdapter;
|
BluetoothAdapter mBluetoothAdapter;
|
||||||
private final boolean mShowDevicesWithoutNames;
|
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 final int mType;
|
||||||
|
|
||||||
private AlertDialog mDisconnectDialog;
|
private AlertDialog mDisconnectDialog;
|
||||||
@@ -127,8 +132,9 @@ public final class BluetoothDevicePreference extends GearPreference {
|
|||||||
|
|
||||||
mCachedDevice = cachedDevice;
|
mCachedDevice = cachedDevice;
|
||||||
mCallback = new BluetoothDevicePreferenceCallback();
|
mCallback = new BluetoothDevicePreferenceCallback();
|
||||||
mCurrentTime = System.currentTimeMillis();
|
mId = sNextId.getAndIncrement();
|
||||||
mType = type;
|
mType = type;
|
||||||
|
setVisible(false);
|
||||||
|
|
||||||
onPreferenceAttributesChanged();
|
onPreferenceAttributesChanged();
|
||||||
}
|
}
|
||||||
@@ -229,35 +235,41 @@ public final class BluetoothDevicePreference extends GearPreference {
|
|||||||
|
|
||||||
@SuppressWarnings("FutureReturnValueIgnored")
|
@SuppressWarnings("FutureReturnValueIgnored")
|
||||||
void onPreferenceAttributesChanged() {
|
void onPreferenceAttributesChanged() {
|
||||||
Pair<Drawable, String> pair = mCachedDevice.getDrawableWithDescription();
|
try {
|
||||||
setIcon(pair.first);
|
ThreadUtils.postOnBackgroundThread(() -> {
|
||||||
contentDescription = pair.second;
|
@Nullable String name = mCachedDevice.getName();
|
||||||
|
// Null check is done at the framework
|
||||||
|
@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
|
* The preference framework takes care of making sure the value has
|
||||||
* changed before proceeding. It will also call notifyChanged() if
|
* changed before proceeding. It will also call notifyChanged() if
|
||||||
* any preference info has changed from the previous value.
|
* any preference info has changed from the previous value.
|
||||||
*/
|
*/
|
||||||
setTitle(mCachedDevice.getName());
|
setTitle(name);
|
||||||
try {
|
setSummary(connectionSummary);
|
||||||
ThreadUtils.postOnBackgroundThread(() -> {
|
setIcon(pair.first);
|
||||||
// Null check is done at the framework
|
contentDescription = pair.second;
|
||||||
ThreadUtils.postOnMainThread(() -> setSummary(getConnectionSummary()));
|
|
||||||
});
|
|
||||||
} catch (RejectedExecutionException e) {
|
|
||||||
Log.w(TAG, "Handler thread unavailable, skipping getConnectionSummary!");
|
|
||||||
}
|
|
||||||
// Used to gray out the item
|
// Used to gray out the item
|
||||||
setEnabled(!mCachedDevice.isBusy());
|
setEnabled(!isBusy);
|
||||||
|
setVisible(isVisible);
|
||||||
// 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
|
// This could affect ordering, so notify that
|
||||||
if (mNeedNotifyHierarchyChanged) {
|
if (mNeedNotifyHierarchyChanged) {
|
||||||
notifyHierarchyChanged();
|
notifyHierarchyChanged();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (RejectedExecutionException e) {
|
||||||
|
Log.w(TAG, "Handler thread unavailable, skipping getConnectionSummary!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -311,7 +323,7 @@ public final class BluetoothDevicePreference extends GearPreference {
|
|||||||
return mCachedDevice
|
return mCachedDevice
|
||||||
.compareTo(((BluetoothDevicePreference) another).mCachedDevice);
|
.compareTo(((BluetoothDevicePreference) another).mCachedDevice);
|
||||||
case SortType.TYPE_FIFO:
|
case SortType.TYPE_FIFO:
|
||||||
return mCurrentTime > ((BluetoothDevicePreference) another).mCurrentTime ? 1 : -1;
|
return mId > ((BluetoothDevicePreference) another).mId ? 1 : -1;
|
||||||
default:
|
default:
|
||||||
return super.compareTo(another);
|
return super.compareTo(another);
|
||||||
}
|
}
|
||||||
|
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
@@ -202,7 +202,7 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
|
new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
|
||||||
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
|
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
|
||||||
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
|
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.isConnected()).thenReturn(true);
|
||||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
||||||
@@ -210,7 +210,7 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
mFragment.onProfileConnectionStateChanged(mCachedBluetoothDevice,
|
||||||
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
|
BluetoothProfile.A2DP, BluetoothAdapter.STATE_CONNECTED);
|
||||||
|
|
||||||
assertThat(mFragment.mDevicePreferenceMap.size()).isEqualTo(0);
|
assertThat(mFragment.getDevicePreferenceMap().size()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -221,7 +221,7 @@ public class BluetoothDevicePairingDetailBaseTest {
|
|||||||
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
|
true, BluetoothDevicePreference.SortType.TYPE_FIFO);
|
||||||
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
|
final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS);
|
||||||
final BluetoothDevice device2 = mBluetoothAdapter.getRemoteDevice(TEST_DEVICE_ADDRESS_B);
|
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.isConnected()).thenReturn(true);
|
||||||
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
when(mCachedBluetoothDevice.getDevice()).thenReturn(device);
|
||||||
|
@@ -27,7 +27,12 @@ import static org.mockito.Mockito.verify;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
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 androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
|
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
|
||||||
@@ -53,6 +58,20 @@ public class BluetoothPairingDetailTest {
|
|||||||
|
|
||||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
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)
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
private LocalBluetoothManager mLocalManager;
|
private LocalBluetoothManager mLocalManager;
|
||||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
@@ -74,6 +93,8 @@ public class BluetoothPairingDetailTest {
|
|||||||
.findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
|
.findPreference(BluetoothPairingDetail.KEY_AVAIL_DEVICES);
|
||||||
doReturn(mFooterPreference).when(mFragment)
|
doReturn(mFooterPreference).when(mFragment)
|
||||||
.findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
|
.findPreference(BluetoothPairingDetail.KEY_FOOTER_PREF);
|
||||||
|
doReturn(new View(mContext)).when(mFragment).getView();
|
||||||
|
doReturn((LifecycleOwner) () -> mFakeLifecycle).when(mFragment).getViewLifecycleOwner();
|
||||||
doReturn(Collections.emptyList()).when(mDeviceManager).getCachedDevicesCopy();
|
doReturn(Collections.emptyList()).when(mDeviceManager).getCachedDevicesCopy();
|
||||||
|
|
||||||
mFragment.mBluetoothAdapter = mBluetoothAdapter;
|
mFragment.mBluetoothAdapter = mBluetoothAdapter;
|
||||||
@@ -82,7 +103,7 @@ public class BluetoothPairingDetailTest {
|
|||||||
mFragment.mDeviceListGroup = mAvailableDevicesCategory;
|
mFragment.mDeviceListGroup = mAvailableDevicesCategory;
|
||||||
mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY);
|
mFragment.onViewCreated(mFragment.getView(), Bundle.EMPTY);
|
||||||
}
|
}
|
||||||
//
|
|
||||||
@Test
|
@Test
|
||||||
public void initPreferencesFromPreferenceScreen_findPreferences() {
|
public void initPreferencesFromPreferenceScreen_findPreferences() {
|
||||||
mFragment.initPreferencesFromPreferenceScreen();
|
mFragment.initPreferencesFromPreferenceScreen();
|
||||||
|
Reference in New Issue
Block a user