Scan and notify apps when their companion devices are nearby

Test: manual
Bug: 168052577
Change-Id: Ib2187fb76e604878b1d4dd9c0cd6cea610b2a04d
This commit is contained in:
Eugene Susla
2021-01-12 11:39:43 -08:00
parent 68c395f4c7
commit 017c2c4145
3 changed files with 366 additions and 30 deletions

View File

@@ -3197,6 +3197,61 @@ public final class BluetoothAdapter {
void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord);
}
/**
* Register a callback to receive events whenever the bluetooth stack goes down and back up,
* e.g. in the event the bluetooth is turned off/on via settings.
*
* If the bluetooth stack is currently up, there will not be an initial callback call.
* You can use the return value as an indication of this being the case.
*
* Callbacks will be delivered on a binder thread.
*
* @return whether bluetooth is already up currently
*
* @hide
*/
public boolean registerServiceLifecycleCallback(ServiceLifecycleCallback callback) {
return getBluetoothService(callback.mRemote) != null;
}
/**
* Unregister a callback registered via {@link #registerServiceLifecycleCallback}
*
* @hide
*/
public void unregisterServiceLifecycleCallback(ServiceLifecycleCallback callback) {
removeServiceStateCallback(callback.mRemote);
}
/**
* A callback for {@link #registerServiceLifecycleCallback}
*
* @hide
*/
public abstract static class ServiceLifecycleCallback {
/** Called when the bluetooth stack is up */
public abstract void onBluetoothServiceUp();
/** Called when the bluetooth stack is down */
public abstract void onBluetoothServiceDown();
IBluetoothManagerCallback mRemote = new IBluetoothManagerCallback.Stub() {
@Override
public void onBluetoothServiceUp(IBluetooth bluetoothService) {
ServiceLifecycleCallback.this.onBluetoothServiceUp();
}
@Override
public void onBluetoothServiceDown() {
ServiceLifecycleCallback.this.onBluetoothServiceDown();
}
@Override
public void onBrEdrDown() {}
};
}
/**
* Starts a scan for Bluetooth LE devices.
*

View File

@@ -38,7 +38,7 @@ public final class Association implements Parcelable {
private final @NonNull String mDeviceMacAddress;
private final @NonNull String mPackageName;
private final @Nullable String mDeviceProfile;
private final boolean mKeepProfilePrivilegesWhenDeviceAway;
private final boolean mNotifyOnDeviceNearby;
/** @hide */
public int getUserId() {
@@ -47,7 +47,7 @@ public final class Association implements Parcelable {
// Code below generated by codegen v1.0.21.
// Code below generated by codegen v1.0.22.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -71,7 +71,7 @@ public final class Association implements Parcelable {
@NonNull String deviceMacAddress,
@NonNull String packageName,
@Nullable String deviceProfile,
boolean keepProfilePrivilegesWhenDeviceAway) {
boolean notifyOnDeviceNearby) {
this.mUserId = userId;
com.android.internal.util.AnnotationValidations.validate(
UserIdInt.class, null, mUserId);
@@ -82,7 +82,7 @@ public final class Association implements Parcelable {
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPackageName);
this.mDeviceProfile = deviceProfile;
this.mKeepProfilePrivilegesWhenDeviceAway = keepProfilePrivilegesWhenDeviceAway;
this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
// onConstructed(); // You can define this method to get a callback
}
@@ -103,8 +103,8 @@ public final class Association implements Parcelable {
}
@DataClass.Generated.Member
public boolean isKeepProfilePrivilegesWhenDeviceAway() {
return mKeepProfilePrivilegesWhenDeviceAway;
public boolean isNotifyOnDeviceNearby() {
return mNotifyOnDeviceNearby;
}
@Override
@@ -118,7 +118,7 @@ public final class Association implements Parcelable {
"deviceMacAddress = " + mDeviceMacAddress + ", " +
"packageName = " + mPackageName + ", " +
"deviceProfile = " + mDeviceProfile + ", " +
"keepProfilePrivilegesWhenDeviceAway = " + mKeepProfilePrivilegesWhenDeviceAway +
"notifyOnDeviceNearby = " + mNotifyOnDeviceNearby +
" }";
}
@@ -139,7 +139,7 @@ public final class Association implements Parcelable {
&& Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
&& Objects.equals(mPackageName, that.mPackageName)
&& Objects.equals(mDeviceProfile, that.mDeviceProfile)
&& mKeepProfilePrivilegesWhenDeviceAway == that.mKeepProfilePrivilegesWhenDeviceAway;
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby;
}
@Override
@@ -153,7 +153,7 @@ public final class Association implements Parcelable {
_hash = 31 * _hash + Objects.hashCode(mDeviceMacAddress);
_hash = 31 * _hash + Objects.hashCode(mPackageName);
_hash = 31 * _hash + Objects.hashCode(mDeviceProfile);
_hash = 31 * _hash + Boolean.hashCode(mKeepProfilePrivilegesWhenDeviceAway);
_hash = 31 * _hash + Boolean.hashCode(mNotifyOnDeviceNearby);
return _hash;
}
@@ -164,7 +164,7 @@ public final class Association implements Parcelable {
// void parcelFieldName(Parcel dest, int flags) { ... }
byte flg = 0;
if (mKeepProfilePrivilegesWhenDeviceAway) flg |= 0x10;
if (mNotifyOnDeviceNearby) flg |= 0x10;
if (mDeviceProfile != null) flg |= 0x8;
dest.writeByte(flg);
dest.writeInt(mUserId);
@@ -185,7 +185,7 @@ public final class Association implements Parcelable {
// static FieldType unparcelFieldName(Parcel in) { ... }
byte flg = in.readByte();
boolean keepProfilePrivilegesWhenDeviceAway = (flg & 0x10) != 0;
boolean notifyOnDeviceNearby = (flg & 0x10) != 0;
int userId = in.readInt();
String deviceMacAddress = in.readString();
String packageName = in.readString();
@@ -201,7 +201,7 @@ public final class Association implements Parcelable {
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPackageName);
this.mDeviceProfile = deviceProfile;
this.mKeepProfilePrivilegesWhenDeviceAway = keepProfilePrivilegesWhenDeviceAway;
this.mNotifyOnDeviceNearby = notifyOnDeviceNearby;
// onConstructed(); // You can define this method to get a callback
}
@@ -221,10 +221,10 @@ public final class Association implements Parcelable {
};
@DataClass.Generated(
time = 1606940835778L,
codegenVersion = "1.0.21",
time = 1610482674799L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/companion/Association.java",
inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mKeepProfilePrivilegesWhenDeviceAway\npublic int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mNotifyOnDeviceNearby\npublic int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}

View File

@@ -17,9 +17,14 @@
package com.android.server.companion;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
import static android.content.Context.BIND_IMPORTANT;
import static com.android.internal.util.CollectionUtils.emptyIfNull;
import static com.android.internal.util.CollectionUtils.find;
import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.internal.util.CollectionUtils.map;
import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
@@ -40,12 +45,18 @@ import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.companion.CompanionDeviceService;
import android.companion.DeviceNotAssociatedException;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceManager;
import android.companion.ICompanionDeviceService;
import android.companion.IFindDeviceCallback;
import android.content.ComponentName;
import android.content.Context;
@@ -77,6 +88,7 @@ import android.permission.PermissionControllerManager;
import android.provider.Settings;
import android.provider.SettingsStringUtil.ComponentNameSet;
import android.text.BidiFormatter;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.ExceptionUtils;
@@ -115,6 +127,7 @@ import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -135,6 +148,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
".DeviceDiscoveryService");
private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000;
private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000;
private static final boolean DEBUG = false;
private static final String LOG_TAG = "CompanionDeviceManagerService";
@@ -146,18 +162,23 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
private static final String XML_ATTR_PACKAGE = "package";
private static final String XML_ATTR_DEVICE = "device";
private static final String XML_ATTR_PROFILE = "profile";
private static final String XML_ATTR_PERSISTENT_PROFILE_GRANTS = "persistent_profile_grants";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
private static final String XML_FILE_NAME = "companion_device_manager_associations.xml";
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
private PowerWhitelistManager mPowerWhitelistManager;
private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
/** userId -> packageName -> serviceConnector */
private PerUser<ArrayMap<String, ServiceConnector<ICompanionDeviceService>>>
mDeviceListenerServiceConnectors;
private IAppOpsService mAppOpsManager;
private RoleManager mRoleManager;
private BluetoothAdapter mBluetoothAdapter;
private UserManager mUserManager;
private IFindDeviceCallback mFindDeviceCallback;
private ScanCallback mBleScanCallback = new BleScanCallback();
private AssociationRequest mRequest;
private String mCallingPackage;
private AndroidFuture<Association> mOngoingDeviceDiscovery;
@@ -166,8 +187,14 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener =
new BluetoothDeviceConnectedListener();
private List<String> mCurrentlyConnectedDevices = new ArrayList<>();
private ArrayMap<String, Date> mDevicesLastNearby = new ArrayMap<>();
private UnbindDeviceListenersRunnable
mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable();
private ArrayMap<String, TriggerDeviceDisappearedRunnable> mTriggerDeviceDisappearedRunnables =
new ArrayMap<>();
private final Object mLock = new Object();
private final Handler mMainHandler = Handler.getMain();
/** userId -> [association] */
@GuardedBy("mLock")
@@ -189,6 +216,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mPermissionControllerManager = requireNonNull(
context.getSystemService(PermissionControllerManager.class));
mUserManager = context.getSystemService(UserManager.class);
Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@@ -201,6 +229,16 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
}
};
mDeviceListenerServiceConnectors = new PerUser<ArrayMap<String,
ServiceConnector<ICompanionDeviceService>>>() {
@NonNull
@Override
protected ArrayMap<String, ServiceConnector<ICompanionDeviceService>> create(
int userId) {
return new ArrayMap<>();
}
};
registerPackageMonitor();
}
@@ -208,10 +246,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
int userId = getChangingUserId();
updateAssociations(
as -> CollectionUtils.filter(as,
a -> !Objects.equals(a.getPackageName(), packageName)),
getChangingUserId());
userId);
unbindDevicePresenceListener(packageName, userId);
}
@Override
@@ -225,6 +266,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
}.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true);
}
private void unbindDevicePresenceListener(String packageName, int userId) {
ServiceConnector<ICompanionDeviceService> deviceListener =
mDeviceListenerServiceConnectors.forUser(userId)
.remove(packageName);
if (deviceListener != null) {
deviceListener.unbind();
}
}
@Override
public void onStart() {
publishBinderService(Context.COMPANION_DEVICE_SERVICE, mImpl);
@@ -238,6 +288,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
mBluetoothAdapter.registerBluetoothConnectionCallback(
getContext().getMainExecutor(),
mBluetoothDeviceConnectedListener);
initBleScanning();
} else {
Log.w(LOG_TAG, "No BluetoothAdapter available");
}
}
}
@@ -287,7 +340,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
@Override
public void binderDied() {
Handler.getMain().post(this::cleanup);
mMainHandler.post(this::cleanup);
}
private void cleanup() {
@@ -399,7 +452,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
checkCallerIsSystemOr(callingPackage, userId);
checkUsesFeature(callingPackage, getCallingUserId());
}
return new ArrayList<>(CollectionUtils.map(
return new ArrayList<>(map(
getAllAssociations(userId, callingPackage),
a -> a.getDeviceMacAddress()));
}
@@ -506,22 +559,18 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
public void registerDevicePresenceListenerService(
String packageName, String deviceAddress)
throws RemoteException {
checkCanRegisterObserverService(packageName, deviceAddress);
//TODO(eugenesusla) implement
registerDevicePresenceListenerActive(packageName, deviceAddress, true);
}
@Override
public void unregisterDevicePresenceListenerService(
String packageName, String deviceAddress)
throws RemoteException {
checkCanRegisterObserverService(packageName, deviceAddress);
//TODO(eugenesusla) implement
registerDevicePresenceListenerActive(packageName, deviceAddress, false);
}
private void checkCanRegisterObserverService(String packageName, String deviceAddress)
throws RemoteException {
private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
boolean active) throws RemoteException {
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE,
"[un]registerDevicePresenceListenerService");
@@ -537,6 +586,20 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
+ " is not associated with device " + deviceAddress
+ " for user " + userId));
}
updateAssociations(associations -> map(associations, association -> {
if (Objects.equals(association.getPackageName(), packageName)
&& Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
return new Association(
association.getUserId(),
association.getDeviceMacAddress(),
association.getPackageName(),
association.getDeviceProfile(),
active /* notifyOnDeviceNearby */);
} else {
return association;
}
}));
}
private void checkCanCallNotificationApi(String callingPackage) throws RemoteException {
@@ -693,6 +756,10 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddress())) {
grantDeviceProfile(association);
}
if (association.isNotifyOnDeviceNearby()) {
restartBleScan();
}
}
private void exemptFromAutoRevoke(String packageName, int uid) {
@@ -795,9 +862,9 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
association.getDeviceMacAddress());
if (association.getDeviceProfile() != null) {
tag.attribute(null, XML_ATTR_PROFILE, association.getDeviceProfile());
tag.attribute(null, XML_ATTR_PERSISTENT_PROFILE_GRANTS,
tag.attribute(null, XML_ATTR_NOTIFY_DEVICE_NEARBY,
Boolean.toString(
association.isKeepProfilePrivilegesWhenDeviceAway()));
association.isNotifyOnDeviceNearby()));
}
tag.endTag(null, XML_TAG_ASSOCIATION);
});
@@ -845,6 +912,14 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
a -> Objects.equals(packageFilter, a.getPackageName()));
}
private Set<Association> getAllAssociations() {
ArraySet<Association> result = new ArraySet<>();
for (UserInfo user : mUserManager.getAliveUsers()) {
result.addAll(getAllAssociations(user.id));
}
return result;
}
private Set<Association> readAllAssociations(int userId) {
final AtomicFile file = getStorageFileForUser(userId);
@@ -865,7 +940,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
final String profile = parser.getAttributeValue(null, XML_ATTR_PROFILE);
final boolean persistentGrants = Boolean.valueOf(
parser.getAttributeValue(null, XML_ATTR_PERSISTENT_PROFILE_GRANTS));
parser.getAttributeValue(null, XML_ATTR_NOTIFY_DEVICE_NEARBY));
if (appPackage == null || deviceAddress == null) continue;
@@ -896,6 +971,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
}
}
}
onDeviceNearby(address);
}
private void grantDeviceProfile(Association association) {
@@ -919,6 +996,210 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
void onDeviceDisconnected(String address) {
mCurrentlyConnectedDevices.remove(address);
onDeviceDisappeared(address);
}
private ServiceConnector<ICompanionDeviceService> getDeviceListenerServiceConnector(
Association a) {
return mDeviceListenerServiceConnectors.forUser(a.getUserId()).computeIfAbsent(
a.getPackageName(),
pkg -> new ServiceConnector.Impl<>(getContext(),
new Intent(CompanionDeviceService.SERVICE_INTERFACE),
BIND_IMPORTANT,
a.getUserId(),
ICompanionDeviceService.Stub::asInterface));
}
private class BleScanCallback extends ScanCallback {
@Override
public void onScanResult(int callbackType, ScanResult result) {
if (DEBUG) {
Log.i(LOG_TAG, "onScanResult(callbackType = "
+ callbackType + ", result = " + result + ")");
}
onDeviceNearby(result.getDevice().getAddress());
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
for (int i = 0, size = results.size(); i < size; i++) {
onScanResult(CALLBACK_TYPE_ALL_MATCHES, results.get(i));
}
}
@Override
public void onScanFailed(int errorCode) {
if (errorCode == SCAN_FAILED_ALREADY_STARTED) {
// ignore - this might happen if BT tries to auto-restore scans for us in the
// future
} else {
Log.wtf(LOG_TAG, "Failed to start BLE scan: error " + errorCode);
}
}
}
private class UnbindDeviceListenersRunnable implements Runnable {
public String getJobId(String address) {
return "CDM_deviceGone_unbind_" + address;
}
@Override
public void run() {
int size = mDevicesLastNearby.size();
for (int i = 0; i < size; i++) {
String address = mDevicesLastNearby.keyAt(i);
Date lastNearby = mDevicesLastNearby.valueAt(i);
if (System.currentTimeMillis() - lastNearby.getTime()
>= DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS) {
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
getDeviceListenerServiceConnector(association).unbind();
}
}
}
}
}
}
private class TriggerDeviceDisappearedRunnable implements Runnable {
private final String mAddress;
TriggerDeviceDisappearedRunnable(String address) {
mAddress = address;
}
public void schedule() {
mMainHandler.removeCallbacks(this);
mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS);
}
@Override
public void run() {
onDeviceDisappeared(mAddress);
}
}
private Set<Association> getAllAssociations(String deviceAddress) {
List<UserInfo> aliveUsers = mUserManager.getAliveUsers();
Set<Association> result = new ArraySet<>();
for (int i = 0, size = aliveUsers.size(); i < size; i++) {
UserInfo user = aliveUsers.get(i);
for (Association association : getAllAssociations(user.id)) {
if (Objects.equals(association.getDeviceMacAddress(), deviceAddress)) {
result.add(association);
}
}
}
return result;
}
private void onDeviceNearby(String address) {
Date timestamp = new Date();
mDevicesLastNearby.put(address, timestamp);
cancelUnbindDeviceListener(address);
mTriggerDeviceDisappearedRunnables
.computeIfAbsent(address, addr -> new TriggerDeviceDisappearedRunnable(address))
.schedule();
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
if (DEBUG) {
Log.i(LOG_TAG, "Device " + address
+ " managed by " + association.getPackageName()
+ " is nearby on " + timestamp);
}
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceAppeared(association.getDeviceMacAddress()));
}
}
}
private void onDeviceDisappeared(String address) {
boolean hasDeviceListeners = false;
for (Association association : getAllAssociations(address)) {
if (association.isNotifyOnDeviceNearby()) {
if (DEBUG) {
Log.i(LOG_TAG, "Device " + address
+ " managed by " + association.getPackageName()
+ " disappeared; last seen on " + mDevicesLastNearby.get(address));
}
getDeviceListenerServiceConnector(association).run(
service -> service.onDeviceDisappeared(address));
hasDeviceListeners = true;
}
}
cancelUnbindDeviceListener(address);
if (hasDeviceListeners) {
mMainHandler.postDelayed(
mUnbindDeviceListenersRunnable,
mUnbindDeviceListenersRunnable.getJobId(address),
DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS);
}
}
private void cancelUnbindDeviceListener(String address) {
mMainHandler.removeCallbacks(
mUnbindDeviceListenersRunnable, mUnbindDeviceListenersRunnable.getJobId(address));
}
private void initBleScanning() {
boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback(
new BluetoothAdapter.ServiceLifecycleCallback() {
@Override
public void onBluetoothServiceUp() {
Log.i(LOG_TAG, "Bluetooth stack is up");
startBleScan();
}
@Override
public void onBluetoothServiceDown() {
Log.w(LOG_TAG, "Bluetooth stack is down");
}
});
if (bluetoothReady) {
startBleScan();
}
}
void startBleScan() {
List<ScanFilter> filters = getBleScanFilters();
if (filters.isEmpty()) {
return;
}
mBluetoothAdapter.getBluetoothLeScanner().startScan(
filters,
new ScanSettings.Builder().setScanMode(SCAN_MODE_BALANCED).build(),
mBleScanCallback);
}
void restartBleScan() {
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback);
startBleScan();
}
private List<ScanFilter> getBleScanFilters() {
ArrayList<ScanFilter> result = new ArrayList<>();
ArraySet<String> addressesSeen = new ArraySet<>();
for (Association association : getAllAssociations()) {
String address = association.getDeviceMacAddress();
if (addressesSeen.contains(address)) {
continue;
}
if (association.isNotifyOnDeviceNearby()) {
result.add(new ScanFilter.Builder().setDeviceAddress(address).build());
addressesSeen.add(address);
}
}
return result;
}
private AndroidFuture<String> getDeviceProfilePermissionDescription(String deviceProfile) {