Scan and notify apps when their companion devices are nearby
Test: manual Bug: 168052577 Change-Id: Ib2187fb76e604878b1d4dd9c0cd6cea610b2a04d
This commit is contained in:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user