summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java55
-rw-r--r--core/java/android/companion/Association.java30
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java311
3 files changed, 366 insertions, 30 deletions
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 7eda50e5c9cb..ea7e5ea7c802 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -3198,6 +3198,61 @@ public final class BluetoothAdapter {
}
/**
+ * 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.
*
* <p>Results of the scan are reported using the
diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java
index 17bf11b3d4ef..960a08755cb8 100644
--- a/core/java/android/companion/Association.java
+++ b/core/java/android/companion/Association.java
@@ -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() {}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 55e3ef2e427f..a36e7acd6612 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -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) {