Merge "Introduce CDM device presence base on UUID" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 671956d..25e93ce8 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -274,6 +274,7 @@
field public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
field public static final String REQUEST_INSTALL_PACKAGES = "android.permission.REQUEST_INSTALL_PACKAGES";
field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
+ field @FlaggedApi("android.companion.flags.device_presence") public static final String REQUEST_OBSERVE_DEVICE_UUID_PRESENCE = "android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE";
field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final String RUN_USER_INITIATED_JOBS = "android.permission.RUN_USER_INITIATED_JOBS";
@@ -9696,8 +9697,10 @@
method public void requestNotificationAccess(android.content.ComponentName);
method @FlaggedApi("android.companion.association_tag") public void setAssociationTag(int, @NonNull String);
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+ method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
+ method @FlaggedApi("android.companion.device_presence") @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull android.companion.ObservingDevicePresenceRequest);
field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
field public static final int FLAG_CALL_METADATA = 1; // 0x1
@@ -9725,13 +9728,7 @@
method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
- method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int);
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_CONNECTED = 2; // 0x2
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BT_DISCONNECTED = 3; // 0x3
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
- field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+ method @FlaggedApi("android.companion.device_presence") @MainThread public void onDevicePresenceEvent(@NonNull android.companion.DevicePresenceEvent);
field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
}
@@ -9744,6 +9741,38 @@
public class DeviceNotAssociatedException extends java.lang.RuntimeException {
}
+ @FlaggedApi("android.companion.device_presence") public final class DevicePresenceEvent implements android.os.Parcelable {
+ ctor public DevicePresenceEvent(int, int, @Nullable android.os.ParcelUuid);
+ method public int describeContents();
+ method public int getAssociationId();
+ method public int getEvent();
+ method @Nullable public android.os.ParcelUuid getUuid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.DevicePresenceEvent> CREATOR;
+ field public static final int EVENT_BLE_APPEARED = 0; // 0x0
+ field public static final int EVENT_BLE_DISAPPEARED = 1; // 0x1
+ field public static final int EVENT_BT_CONNECTED = 2; // 0x2
+ field public static final int EVENT_BT_DISCONNECTED = 3; // 0x3
+ field public static final int EVENT_SELF_MANAGED_APPEARED = 4; // 0x4
+ field public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5; // 0x5
+ field public static final int NO_ASSOCIATION = -1; // 0xffffffff
+ }
+
+ @FlaggedApi("android.companion.device_presence") public final class ObservingDevicePresenceRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getAssociationId();
+ method @Nullable public android.os.ParcelUuid getUuid();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.ObservingDevicePresenceRequest> CREATOR;
+ }
+
+ public static final class ObservingDevicePresenceRequest.Builder {
+ ctor public ObservingDevicePresenceRequest.Builder();
+ method @NonNull public android.companion.ObservingDevicePresenceRequest build();
+ method @NonNull public android.companion.ObservingDevicePresenceRequest.Builder setAssociationId(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE) public android.companion.ObservingDevicePresenceRequest.Builder setUuid(@NonNull android.os.ParcelUuid);
+ }
+
public final class WifiDeviceFilter implements android.companion.DeviceFilter<android.net.wifi.ScanResult> {
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 672e3439..d743992 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1038,6 +1038,7 @@
}
}
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Register to receive callbacks whenever the associated device comes in and out of range.
*
@@ -1094,7 +1095,7 @@
callingUid, callingPid);
}
}
-
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Unregister for receiving callbacks whenever the associated device comes in and out of range.
*
@@ -1137,6 +1138,64 @@
}
/**
+ * Register to receive callbacks whenever the associated device comes in and out of range.
+ *
+ * <p>The app doesn't need to remain running in order to receive its callbacks.</p>
+ *
+ * <p>Calling app must check for feature presence of
+ * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API.</p>
+ *
+ * <p>For Bluetooth LE devices, this is based on scanning for device with the given address.
+ * The system will scan for the device when Bluetooth is ON or Bluetooth scanning is ON.</p>
+ *
+ * <p>For Bluetooth classic devices this is triggered when the device connects/disconnects.</p>
+ *
+ * <p>WiFi devices are not supported.</p>
+ *
+ * <p>If a Bluetooth LE device wants to use a rotating mac address, it is recommended to use
+ * Resolvable Private Address, and ensure the device is bonded to the phone so that android OS
+ * is able to resolve the address.</p>
+ *
+ * @param request A request for setting the types of device for observing device presence.
+ *
+ * @see ObservingDevicePresenceRequest.Builder
+ * @see CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)
+ */
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void startObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+
+ try {
+ mService.startObservingDevicePresence(
+ request, mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregister for receiving callbacks whenever the associated device comes in and out of range.
+ *
+ * Calling app must check for feature presence of
+ * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} before calling this API.
+ *
+ * @param request A request for setting the types of device for observing device presence.
+ */
+ @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+ @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void stopObservingDevicePresence(@NonNull ObservingDevicePresenceRequest request) {
+ Objects.requireNonNull(request, "request cannot be null");
+
+ try {
+ mService.stopObservingDevicePresence(
+ request, mContext.getOpPackageName(), mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Dispatch a message to system for processing. It should only be called by
* {@link CompanionDeviceService#dispatchMessageToSystem(int, int, byte[])}
*
diff --git a/core/java/android/companion/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java
index 4d0267c..5ad2348 100644
--- a/core/java/android/companion/CompanionDeviceService.java
+++ b/core/java/android/companion/CompanionDeviceService.java
@@ -18,7 +18,6 @@
package android.companion;
import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,8 +32,6 @@
import java.io.InputStream;
import java.io.OutputStream;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -123,62 +120,6 @@
*/
public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
- /** @hide */
- @IntDef(prefix = {"DEVICE_EVENT"}, value = {
- DEVICE_EVENT_BLE_APPEARED,
- DEVICE_EVENT_BLE_DISAPPEARED,
- DEVICE_EVENT_BT_CONNECTED,
- DEVICE_EVENT_BT_DISCONNECTED,
- DEVICE_EVENT_SELF_MANAGED_APPEARED,
- DEVICE_EVENT_SELF_MANAGED_DISAPPEARED
- })
-
- @Retention(RetentionPolicy.SOURCE)
- public @interface DeviceEvent {}
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event if the device comes into BLE range.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BLE_APPEARED = 0;
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event if the device is no longer in BLE range.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1;
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event when the bluetooth device is connected.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BT_CONNECTED = 2;
-
- /**
- * Companion app receives {@link #onDeviceEvent(AssociationInfo, int)} callback
- * with this event if the bluetooth device is disconnected.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_BT_DISCONNECTED = 3;
-
- /**
- * A companion app for a self-managed device will receive the callback
- * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has appeared on its
- * own.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_SELF_MANAGED_APPEARED = 4;
-
- /**
- * A companion app for a self-managed device will receive the callback
- * {@link #onDeviceEvent(AssociationInfo, int)} if it reports that a device has disappeared on
- * its own.
- */
- @FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
- public static final int DEVICE_EVENT_SELF_MANAGED_DISAPPEARED = 5;
private final Stub mRemote = new Stub();
@@ -306,6 +247,7 @@
.detachSystemDataTransport(associationId);
}
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Called by system whenever a device associated with this app is connected.
*
@@ -318,6 +260,7 @@
}
}
+ // TODO(b/315163162) Add @Deprecated keyword after 24Q2 cut.
/**
* Called by system whenever a device associated with this app is disconnected.
*
@@ -331,27 +274,13 @@
}
/**
- * Called by the system during device events.
+ * Called by the system during device events.
*
- * <p>E.g. Event {@link #DEVICE_EVENT_BLE_APPEARED} will be called when the associated
- * companion device comes into BLE range.
- * <p>Event {@link #DEVICE_EVENT_BLE_DISAPPEARED} will be called when the associated
- * companion device is no longer in BLE range.
- * <p> Event {@link #DEVICE_EVENT_BT_CONNECTED} will be called when the associated
- * companion device is connected.
- * <p>Event {@link #DEVICE_EVENT_BT_DISCONNECTED} will be called when the associated
- * companion device is disconnected.
- * Note that app must receive {@link #DEVICE_EVENT_BLE_APPEARED} first before
- * {@link #DEVICE_EVENT_BLE_DISAPPEARED} and {@link #DEVICE_EVENT_BT_CONNECTED}
- * before {@link #DEVICE_EVENT_BT_DISCONNECTED}.
- *
- * @param associationInfo A record for the companion device.
- * @param event Associated companion device's event.
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
*/
@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
@MainThread
- public void onDeviceEvent(@NonNull AssociationInfo associationInfo,
- @DeviceEvent int event) {
+ public void onDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
// Do nothing. Companion apps can override this function.
}
@@ -390,9 +319,10 @@
}
@Override
- public void onDeviceEvent(AssociationInfo associationInfo, int event) {
- mMainHandler.postAtFrontOfQueue(
- () -> mService.onDeviceEvent(associationInfo, event));
+ public void onDevicePresenceEvent(DevicePresenceEvent event) {
+ if (Flags.devicePresence()) {
+ mMainHandler.postAtFrontOfQueue(() -> mService.onDevicePresenceEvent(event));
+ }
}
}
}
diff --git a/core/java/android/companion/DevicePresenceEvent.aidl b/core/java/android/companion/DevicePresenceEvent.aidl
new file mode 100644
index 0000000..1521574
--- /dev/null
+++ b/core/java/android/companion/DevicePresenceEvent.aidl
@@ -0,0 +1,19 @@
+ /*
+ * Copyright (C) 2024 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 android.companion;
+
+ parcelable DevicePresenceEvent;
diff --git a/core/java/android/companion/DevicePresenceEvent.java b/core/java/android/companion/DevicePresenceEvent.java
new file mode 100644
index 0000000..30439a5
--- /dev/null
+++ b/core/java/android/companion/DevicePresenceEvent.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2024 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 android.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Event for observing device presence.
+ *
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
+ * @see ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)
+ * @see ObservingDevicePresenceRequest.Builder#setAssociationId(int)
+ */
+@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+public final class DevicePresenceEvent implements Parcelable {
+
+ /** @hide */
+ @IntDef(prefix = {"EVENT"}, value = {
+ EVENT_BLE_APPEARED,
+ EVENT_BLE_DISAPPEARED,
+ EVENT_BT_CONNECTED,
+ EVENT_BT_DISCONNECTED,
+ EVENT_SELF_MANAGED_APPEARED,
+ EVENT_SELF_MANAGED_DISAPPEARED
+ })
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Event {}
+
+ /**
+ * Indicate observing device presence base on the ParcelUuid but not association id.
+ */
+ public static final int NO_ASSOCIATION = -1;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event if the device comes into BLE range.
+ */
+ public static final int EVENT_BLE_APPEARED = 0;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event if the device is no longer in BLE range.
+ */
+ public static final int EVENT_BLE_DISAPPEARED = 1;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event when the bluetooth device is connected.
+ */
+ public static final int EVENT_BT_CONNECTED = 2;
+
+ /**
+ * Companion app receives
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} callback
+ * with this event if the bluetooth device is disconnected.
+ */
+ public static final int EVENT_BT_DISCONNECTED = 3;
+
+ /**
+ * A companion app for a self-managed device will receive the callback
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}
+ * if it reports that a device has appeared on its
+ * own.
+ */
+ public static final int EVENT_SELF_MANAGED_APPEARED = 4;
+
+ /**
+ * A companion app for a self-managed device will receive the callback
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} if it reports
+ * that a device has disappeared on its own.
+ */
+ public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5;
+ private final int mAssociationId;
+ private final int mEvent;
+ @Nullable
+ private final ParcelUuid mUuid;
+
+ private static final int PARCEL_UUID_NULL = 0;
+
+ private static final int PARCEL_UUID_NOT_NULL = 1;
+
+ /**
+ * Create a new DevicePresenceEvent.
+ */
+ public DevicePresenceEvent(
+ int associationId, @Event int event, @Nullable ParcelUuid uuid) {
+ mAssociationId = associationId;
+ mEvent = event;
+ mUuid = uuid;
+ }
+
+ /**
+ * @return The association id has been used to observe device presence.
+ *
+ * Caller will receive the valid association id if only if using
+ * {@link ObservingDevicePresenceRequest.Builder#setAssociationId(int)}, otherwise
+ * return {@link #NO_ASSOCIATION}.
+ *
+ * @see ObservingDevicePresenceRequest.Builder#setAssociationId(int)
+ */
+ public int getAssociationId() {
+ return mAssociationId;
+ }
+
+ /**
+ * @return Associated companion device's event.
+ */
+ public int getEvent() {
+ return mEvent;
+ }
+
+ /**
+ * @return The ParcelUuid has been used to observe device presence.
+ *
+ * Caller will receive the ParcelUuid if only if using
+ * {@link ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)}, otherwise return null.
+ *
+ * @see ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid)
+ */
+
+ @Nullable
+ public ParcelUuid getUuid() {
+ return mUuid;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAssociationId);
+ dest.writeInt(mEvent);
+ if (mUuid == null) {
+ // Write 0 to the parcel to indicate the ParcelUuid is null.
+ dest.writeInt(PARCEL_UUID_NULL);
+ } else {
+ dest.writeInt(PARCEL_UUID_NOT_NULL);
+ mUuid.writeToParcel(dest, flags);
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DevicePresenceEvent that)) return false;
+
+ return Objects.equals(mUuid, that.mUuid)
+ && mAssociationId == that.mAssociationId
+ && mEvent == that.mEvent;
+ }
+
+ @Override
+ public String toString() {
+ return "ObservingDevicePresenceResult { "
+ + "Association Id= " + mAssociationId + ","
+ + "ParcelUuid= " + mUuid + ","
+ + "Event= " + mEvent + "}";
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAssociationId, mEvent, mUuid);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<DevicePresenceEvent> CREATOR =
+ new Parcelable.Creator<DevicePresenceEvent>() {
+ @Override
+ public DevicePresenceEvent[] newArray(int size) {
+ return new DevicePresenceEvent[size];
+ }
+
+ @Override
+ public DevicePresenceEvent createFromParcel(@NonNull Parcel in) {
+ return new DevicePresenceEvent(in);
+ }
+ };
+
+ private DevicePresenceEvent(@NonNull Parcel in) {
+ mAssociationId = in.readInt();
+ mEvent = in.readInt();
+ if (in.readInt() == PARCEL_UUID_NULL) {
+ mUuid = null;
+ } else {
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in);
+ }
+ }
+}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 22689f3..57d59e5 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -24,8 +24,11 @@
import android.companion.ISystemDataTransferCallback;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.ObservingDevicePresenceRequest;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
+import android.os.ParcelUuid;
+
/**
* Interface for communication with the core companion device manager service.
@@ -132,4 +135,10 @@
byte[] getBackupPayload(int userId);
void applyRestoredPayload(in byte[] payload, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
}
diff --git a/core/java/android/companion/ICompanionDeviceService.aidl b/core/java/android/companion/ICompanionDeviceService.aidl
index 2a311bf..f5401d2 100644
--- a/core/java/android/companion/ICompanionDeviceService.aidl
+++ b/core/java/android/companion/ICompanionDeviceService.aidl
@@ -17,10 +17,12 @@
package android.companion;
import android.companion.AssociationInfo;
+import android.companion.DevicePresenceEvent;
+import android.os.ParcelUuid;
/** @hide */
oneway interface ICompanionDeviceService {
void onDeviceAppeared(in AssociationInfo associationInfo);
void onDeviceDisappeared(in AssociationInfo associationInfo);
- void onDeviceEvent(in AssociationInfo associationInfo, int state);
+ void onDevicePresenceEvent(in DevicePresenceEvent event);
}
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.aidl b/core/java/android/companion/ObservingDevicePresenceRequest.aidl
new file mode 100644
index 0000000..fed0607
--- /dev/null
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.aidl
@@ -0,0 +1,19 @@
+ /*
+ * Copyright (C) 2024 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 android.companion;
+
+ parcelable ObservingDevicePresenceRequest;
\ No newline at end of file
diff --git a/core/java/android/companion/ObservingDevicePresenceRequest.java b/core/java/android/companion/ObservingDevicePresenceRequest.java
new file mode 100644
index 0000000..f1d594e
--- /dev/null
+++ b/core/java/android/companion/ObservingDevicePresenceRequest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2024 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 android.companion;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.provider.OneTimeUseBuilder;
+
+import java.util.Objects;
+
+/**
+ * A request for setting the types of device for observing device presence.
+ *
+ * <p>Only supports association id or ParcelUuid and calling app must declare uses-permission
+ * {@link android.Manifest.permission#REQUEST_OBSERVE_DEVICE_UUID_PRESENCE} if using
+ * {@link Builder#setUuid(ParcelUuid)}.</p>
+ *
+ * Calling apps must use either ObservingDevicePresenceRequest.Builder#setUuid(ParcelUuid) or
+ * ObservingDevicePresenceRequest.Builder#setAssociationId(int), but not both.
+ *
+ * @see Builder#setUuid(ParcelUuid)
+ * @see Builder#setAssociationId(int)
+ * @see CompanionDeviceManager#startObservingDevicePresence(ObservingDevicePresenceRequest)
+ */
+@FlaggedApi(Flags.FLAG_DEVICE_PRESENCE)
+public final class ObservingDevicePresenceRequest implements Parcelable {
+ private final int mAssociationId;
+ @Nullable private final ParcelUuid mUuid;
+
+ private static final int PARCEL_UUID_NULL = 0;
+
+ private static final int PARCEL_UUID_NOT_NULL = 1;
+
+ private ObservingDevicePresenceRequest(int associationId, ParcelUuid uuid) {
+ mAssociationId = associationId;
+ mUuid = uuid;
+ }
+
+ private ObservingDevicePresenceRequest(@NonNull Parcel in) {
+ mAssociationId = in.readInt();
+ if (in.readInt() == PARCEL_UUID_NULL) {
+ mUuid = null;
+ } else {
+ mUuid = ParcelUuid.CREATOR.createFromParcel(in);
+ }
+ }
+
+ /**
+ * @return the association id for observing device presence. It will return
+ * {@link DevicePresenceEvent#NO_ASSOCIATION} if using
+ * {@link Builder#setUuid(ParcelUuid)}.
+ */
+ public int getAssociationId() {
+ return mAssociationId;
+ }
+
+ /**
+ * @return the ParcelUuid for observing device presence.
+ */
+ @Nullable
+ public ParcelUuid getUuid() {
+ return mUuid;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mAssociationId);
+ if (mUuid == null) {
+ // Write 0 to the parcel to indicate the ParcelUuid is null.
+ dest.writeInt(PARCEL_UUID_NULL);
+ } else {
+ dest.writeInt(PARCEL_UUID_NOT_NULL);
+ mUuid.writeToParcel(dest, flags);
+ }
+
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<ObservingDevicePresenceRequest> CREATOR =
+ new Parcelable.Creator<ObservingDevicePresenceRequest>() {
+ @Override
+ public ObservingDevicePresenceRequest[] newArray(int size) {
+ return new ObservingDevicePresenceRequest[size];
+ }
+
+ @Override
+ public ObservingDevicePresenceRequest createFromParcel(@NonNull Parcel in) {
+ return new ObservingDevicePresenceRequest(in);
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "ObservingDevicePresenceRequest { "
+ + "Association Id= " + mAssociationId + ","
+ + "ParcelUuid= " + mUuid + "}";
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ObservingDevicePresenceRequest that)) return false;
+
+ return Objects.equals(mUuid, that.mUuid) && mAssociationId == that.mAssociationId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAssociationId, mUuid);
+ }
+
+ /**
+ * A builder for {@link ObservingDevicePresenceRequest}
+ */
+ public static final class Builder extends OneTimeUseBuilder<ObservingDevicePresenceRequest> {
+ // Initial the association id to {@link DevicePresenceEvent.NO_ASSOCIATION}
+ // to indicate the value is not set yet.
+ private int mAssociationId = DevicePresenceEvent.NO_ASSOCIATION;
+ private ParcelUuid mUuid;
+
+ public Builder() {}
+
+ /**
+ * Set the association id to be observed for device presence.
+ *
+ * <p>The provided device must be {@link CompanionDeviceManager#associate associated}
+ * with the calling app before calling this method if using this API.
+ *
+ * Caller must implement a single {@link CompanionDeviceService} which will be bound to and
+ * receive callbacks to
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}.</p>
+ *
+ * <p>Calling apps must use either {@link #setUuid(ParcelUuid)}
+ * or this API, but not both.</p>
+ *
+ * @param associationId The association id for observing device presence.
+ */
+ @NonNull
+ public Builder setAssociationId(int associationId) {
+ checkNotUsed();
+ this.mAssociationId = associationId;
+ return this;
+ }
+
+ /**
+ * Set the ParcelUuid to be observed for device presence.
+ *
+ * <p>It does not require to create the association before calling this API.
+ * This only supports classic Bluetooth scan and caller must implement
+ * a single {@link CompanionDeviceService} which will be bound to and receive callbacks to
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)}.</p>
+ *
+ * <p>The Uuid should be matching one of the ParcelUuid form
+ * {@link android.bluetooth.BluetoothDevice#getUuids()}</p>
+ *
+ * <p>Calling apps must use either this API or {@link #setAssociationId(int)},
+ * but not both.</p>
+ *
+ * @param uuid The ParcelUuid for observing device presence.
+ */
+ @NonNull
+ @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+ public Builder setUuid(@NonNull ParcelUuid uuid) {
+ checkNotUsed();
+ this.mUuid = uuid;
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public ObservingDevicePresenceRequest build() {
+ markUsed();
+ if (mUuid != null && mAssociationId != DevicePresenceEvent.NO_ASSOCIATION) {
+ throw new IllegalStateException("Cannot observe device presence based on "
+ + "both ParcelUuid and association ID. Choose one or the other.");
+ } else if (mUuid == null && mAssociationId <= 0) {
+ throw new IllegalStateException("Must provide either a ParcelUuid or "
+ + "a valid association ID to observe device presence.");
+ }
+
+ return new ObservingDevicePresenceRequest(mAssociationId, mUuid);
+ }
+ }
+}
diff --git a/core/java/android/companion/flags.aconfig b/core/java/android/companion/flags.aconfig
index 9e410b8..d634b64 100644
--- a/core/java/android/companion/flags.aconfig
+++ b/core/java/android/companion/flags.aconfig
@@ -33,4 +33,4 @@
namespace: "companion"
description: "Expose perm sync user consent API"
bug: "309528663"
-}
\ No newline at end of file
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fea1fc0..f9731d4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5737,6 +5737,14 @@
android:description="@string/permdesc_observeCompanionDevicePresence"
android:protectionLevel="normal" />
+ <!-- Allows an application to subscribe to notifications about the nearby devices' presence
+ status change base on the UUIDs.
+ <p>Not for use by third-party applications.</p>
+ @FlaggedApi("android.companion.flags.device_presence")
+ -->
+ <permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
+ android:protectionLevel="signature|privileged" />
+
<!-- Allows an application to deliver companion messages to system
-->
<permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 294b8ae..97b1ec7 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -421,6 +421,8 @@
<permission name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING" />
<permission name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
<permission name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
+ <permission name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
+
<!-- Permission required for testing registering pull atom callbacks. -->
<permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
<!-- Permission required for testing system audio effect APIs. -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index cc63996..d384542 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -343,6 +343,7 @@
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
<uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" />
+ <uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" />
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
<uses-permission android:name="android.permission.WATCH_APPOPS" />
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index c2d2468..586aa8a 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -22,9 +22,11 @@
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
+import android.os.ParcelUuid;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -46,7 +48,8 @@
* the services, maintaining the connection (the binding), and invoking callback methods such as
* {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
* {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
- * {@link CompanionDeviceService#onDeviceEvent(AssociationInfo, int)} in the application process.
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
+ * application process.
*
* <p>
* The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
@@ -54,7 +57,7 @@
* <ul>
* <li> {@link #bindCompanionApplication(int, String, boolean)}
* <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #notifyCompanionApplicationDeviceEvent(AssociationInfo, int)} (AssociationInfo, int)}
+ * <li> {@link #notifyCompanionApplicationDevicePresenceEvent(AssociationInfo, int)}
* <li> {@link #isCompanionApplicationBound(int, String)}
* <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
* </ul>
@@ -72,6 +75,7 @@
private final @NonNull Context mContext;
private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull CompanionServicesRegister mCompanionServicesRegister;
@@ -82,9 +86,11 @@
private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
CompanionApplicationController(Context context, AssociationStore associationStore,
+ ObservableUuidStore observableUuidStore,
CompanionDevicePresenceMonitor companionDevicePresenceMonitor) {
mContext = context;
mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
mDevicePresenceMonitor = companionDevicePresenceMonitor;
mCompanionServicesRegister = new CompanionServicesRegister();
mBoundCompanionApplications = new AndroidPackageMap<>();
@@ -281,25 +287,50 @@
primaryServiceConnector.postOnDeviceDisappeared(association);
}
- void notifyCompanionApplicationDeviceEvent(AssociationInfo association, int event) {
+ void notifyCompanionApplicationDevicePresenceEvent(AssociationInfo association, int event) {
final int userId = association.getUserId();
final String packageName = association.getPackageName();
final CompanionDeviceServiceConnector primaryServiceConnector =
getPrimaryServiceConnector(userId, packageName);
+ final DevicePresenceEvent devicePresenceEvent =
+ new DevicePresenceEvent(association.getId(), event, null);
if (primaryServiceConnector == null) {
- Slog.e(TAG, "notifyCompanionApplicationDeviceEvent(): "
+ Slog.e(TAG, "notifyCompanionApplicationDevicePresenceEvent(): "
+ "u" + userId + "/" + packageName
+ " event=[ " + event + " ] is NOT bound.");
Slog.e(TAG, "Stacktrace", new Throwable());
return;
}
- Slog.i(TAG, "Calling onDeviceEvent() to userId=[" + userId + "] package=["
+ Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
+ packageName + "] associationId=[" + association.getId()
- + "] state=[" + event + "]");
+ + "] event=[" + event + "]");
- primaryServiceConnector.postOnDeviceEvent(association, event);
+ primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
+ }
+
+ void notifyApplicationDevicePresenceEvent(ObservableUuid uuid, int event) {
+ final int userId = uuid.getUserId();
+ final ParcelUuid parcelUuid = uuid.getUuid();
+ final String packageName = uuid.getPackageName();
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ final DevicePresenceEvent devicePresenceEvent =
+ new DevicePresenceEvent(DevicePresenceEvent.NO_ASSOCIATION, event, parcelUuid);
+
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notifyApplicationDevicePresenceChanged(): "
+ + "u" + userId + "/" + packageName
+ + " event=[ " + event + " ] is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Slog.i(TAG, "Calling onDevicePresenceEvent() to userId=[" + userId + "] package=["
+ + packageName + "]" + "event= [" + event + "]");
+
+ primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
}
void dump(@NonNull PrintWriter out) {
@@ -364,6 +395,9 @@
// Make sure to clean up the state for all the associations
// that associate with this package.
boolean shouldScheduleRebind = false;
+ boolean shouldScheduleRebindForUuid = false;
+ final List<ObservableUuid> uuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
for (AssociationInfo ai :
mAssociationStore.getAssociationsForPackage(userId, packageName)) {
@@ -385,7 +419,14 @@
}
}
- return stillAssociated && shouldScheduleRebind;
+ for (ObservableUuid uuid : uuids) {
+ if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+ shouldScheduleRebindForUuid = true;
+ break;
+ }
+ }
+
+ return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
}
private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 84e1d90..2e01ced 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,17 +20,17 @@
import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
-import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -45,6 +45,7 @@
import static com.android.server.companion.PackageUtils.getPackageInfo;
import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
@@ -75,6 +76,7 @@
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
+import android.companion.ObservingDevicePresenceRequest;
import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
@@ -92,6 +94,7 @@
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelUuid;
import android.os.PowerManagerInternal;
import android.os.PowerWhitelistManager;
import android.os.RemoteCallbackList;
@@ -132,6 +135,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -221,6 +225,8 @@
private CrossDeviceSyncController mCrossDeviceSyncController;
+ private ObservableUuidStore mObservableUuidStore;
+
public CompanionDeviceManagerService(Context context) {
super(context);
@@ -240,6 +246,7 @@
mOnPackageVisibilityChangeListener =
new OnPackageVisibilityChangeListener(mActivityManager);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mObservableUuidStore = new ObservableUuidStore();
}
@Override
@@ -254,13 +261,16 @@
mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
loadAssociationsFromDisk();
+
+ mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
+
mAssociationStore.registerListener(mAssociationStoreChangeListener);
mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(mUserManager,
- mAssociationStore, mDevicePresenceCallback);
+ mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
mCompanionAppController = new CompanionApplicationController(
- context, mAssociationStore, mDevicePresenceMonitor);
+ context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
@@ -352,13 +362,29 @@
final int userId = user.getUserIdentifier();
final Set<BluetoothDevice> blueToothDevices =
mDevicePresenceMonitor.getPendingConnectedDevices().get(userId);
+
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForUser(userId);
+
if (blueToothDevices != null) {
for (BluetoothDevice bluetoothDevice : blueToothDevices) {
+ final List<ParcelUuid> deviceUuids = bluetoothDevice.getUuids() == null
+ ? Collections.emptyList() : Arrays.asList(bluetoothDevice.getUuids());
+
for (AssociationInfo ai:
mAssociationStore.getAssociationsByAddress(bluetoothDevice.getAddress())) {
Slog.i(TAG, "onUserUnlocked, device id( " + ai.getId() + " ) is connected");
mDevicePresenceMonitor.onBluetoothCompanionDeviceConnected(ai.getId());
}
+
+ for (ObservableUuid observableUuid : observableUuids) {
+ if (deviceUuids.contains(observableUuid.getUuid())) {
+ Slog.i(TAG, "onUserUnlocked, UUID( "
+ + observableUuid.getUuid() + " ) is connected");
+ mDevicePresenceMonitor.onDevicePresenceEventByUuid(
+ observableUuid, EVENT_BT_CONNECTED);
+ }
+ }
}
}
}
@@ -423,31 +449,31 @@
}
}
- private void onDeviceEventInternal(int associationId, int event) {
- Slog.i(TAG, "onDeviceEventInternal() id=" + associationId + " event= " + event);
+ private void onDevicePresenceEventInternal(int associationId, int event) {
+ Slog.i(TAG, "onDevicePresenceEventInternal() id=" + associationId + " event= " + event);
final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
final String packageName = association.getPackageName();
final int userId = association.getUserId();
switch (event) {
- case DEVICE_EVENT_BLE_APPEARED:
- case DEVICE_EVENT_BT_CONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_APPEARED:
+ case EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
if (!association.shouldBindWhenPresent()) return;
bindApplicationIfNeeded(association);
- mCompanionAppController.notifyCompanionApplicationDeviceEvent(
+ mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
association, event);
break;
- case DEVICE_EVENT_BLE_DISAPPEARED:
- case DEVICE_EVENT_BT_DISCONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED:
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
return;
}
if (association.shouldBindWhenPresent()) {
- mCompanionAppController.notifyCompanionApplicationDeviceEvent(
+ mCompanionAppController.notifyCompanionApplicationDevicePresenceEvent(
association, event);
}
// Check if there are other devices associated to the app that are present.
@@ -460,6 +486,45 @@
}
}
+ private void onDevicePresenceEventByUuidInternal(ObservableUuid uuid, int event) {
+ Slog.i(TAG, "onDevicePresenceEventByUuidInternal() id=" + uuid.getUuid()
+ + "for package=" + uuid.getPackageName() + " event=" + event);
+ final String packageName = uuid.getPackageName();
+ final int userId = uuid.getUserId();
+
+ switch(event) {
+ case EVENT_BT_CONNECTED:
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ mCompanionAppController.bindCompanionApplication(
+ userId, packageName, /*bindImportant*/ false);
+
+ } else if (DEBUG) {
+ Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
+ }
+
+ mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+
+ break;
+ case EVENT_BT_DISCONNECTED:
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+ return;
+ }
+
+ mCompanionAppController.notifyApplicationDevicePresenceEvent(uuid, event);
+ // Check if there are other devices associated to the app or the UUID to be
+ // observed are present.
+ if (shouldBindPackage(userId, packageName)) return;
+
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + "is not supported");
+ break;
+ }
+ }
+
private void bindApplicationIfNeeded(AssociationInfo association) {
final String packageName = association.getPackageName();
final int userId = association.getUserId();
@@ -476,15 +541,26 @@
/**
* @return whether the package should be bound (i.e. at least one of the devices associated with
- * the package is currently present).
+ * the package is currently present OR the UUID to be observed by this package is
+ * currently present).
*/
private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) {
final List<AssociationInfo> packageAssociations =
mAssociationStore.getAssociationsForPackage(userId, packageName);
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
for (AssociationInfo association : packageAssociations) {
if (!association.shouldBindWhenPresent()) continue;
if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true;
}
+
+ for (ObservableUuid uuid : observableUuids) {
+ if (mDevicePresenceMonitor.isDeviceUuidPresent(uuid.getUuid())) {
+ return true;
+ }
+ }
+
return false;
}
@@ -568,6 +644,8 @@
// Clear associations.
final List<AssociationInfo> associationsForPackage =
mAssociationStore.getAssociationsForPackage(userId, packageName);
+ final List<ObservableUuid> uuidsTobeObserved =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
for (AssociationInfo association : associationsForPackage) {
mAssociationStore.removeAssociation(association.getId());
}
@@ -575,6 +653,10 @@
for (AssociationInfo association : associationsForPackage) {
maybeRemoveRoleHolderForAssociation(association);
}
+ // Clear the uuids to be observed.
+ for (ObservableUuid uuid : uuidsTobeObserved) {
+ mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
+ }
mCompanionAppController.onPackagesChanged(userId);
}
@@ -855,6 +937,95 @@
}
@Override
+ @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ startObservingDevicePresence_enforcePermission();
+ registerDevicePresenceListener(request, packageName, userId, /* active */ true);
+ }
+
+ @Override
+ @EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
+ public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
+ String packageName, int userId) {
+ stopObservingDevicePresence_enforcePermission();
+ registerDevicePresenceListener(request, packageName, userId, /* active */ false);
+ }
+
+ private void registerDevicePresenceListener(ObservingDevicePresenceRequest request,
+ String packageName, int userId, boolean active) {
+ enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
+ enforceCallerIsSystemOr(userId, packageName);
+
+ final int associationId = request.getAssociationId();
+ final AssociationInfo associationInfo = mAssociationStore.getAssociationById(
+ associationId);
+ final ParcelUuid uuid = request.getUuid();
+
+ if (uuid != null) {
+ enforceCallerCanObservingDevicePresenceByUuid(getContext());
+ if (active) {
+ startObservingDevicePresenceByUuid(uuid, packageName, userId);
+ } else {
+ stopObservingDevicePresenceByUuid(uuid, packageName, userId);
+ }
+ } else if (associationInfo == null) {
+ throw new IllegalArgumentException("App " + packageName
+ + " is not associated with device " + request.getAssociationId()
+ + " for user " + userId);
+ } else {
+ processDevicePresenceListener(
+ associationInfo, userId, packageName, active);
+ }
+ }
+
+ private void startObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+ int userId) {
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+
+ for (ObservableUuid observableUuid : observableUuids) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ Slog.i(TAG, "The uuid: " + uuid + " for package:" + packageName
+ + "has been already scheduled for observing");
+ return;
+ }
+ }
+
+ final ObservableUuid observableUuid = new ObservableUuid(userId, uuid,
+ packageName, System.currentTimeMillis());
+
+ mObservableUuidStore.writeObservableUuid(userId, observableUuid);
+ }
+
+ private void stopObservingDevicePresenceByUuid(ParcelUuid uuid, String packageName,
+ int userId) {
+ final List<ObservableUuid> uuidsTobeObserved =
+ mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
+ boolean isScheduledObserving = false;
+
+ for (ObservableUuid observableUuid : uuidsTobeObserved) {
+ if (observableUuid.getUuid().equals(uuid)) {
+ isScheduledObserving = true;
+ break;
+ }
+ }
+
+ if (!isScheduledObserving) {
+ Slog.i(TAG, "The uuid: " + uuid.toString() + " for package:" + packageName
+ + "has NOT been scheduled for observing yet");
+ return;
+ }
+
+ mObservableUuidStore.removeObservableUuid(userId, uuid, packageName);
+ mDevicePresenceMonitor.removeCurrentConnectedUuidDevice(uuid);
+
+ if (!shouldBindPackage(userId, packageName)) {
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ }
+ }
+
+ @Override
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
int userId, int associationId) {
return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
@@ -1002,6 +1173,11 @@
+ " for user " + userId));
}
+ processDevicePresenceListener(association, userId, packageName, active);
+ }
+
+ private void processDevicePresenceListener(AssociationInfo association,
+ int userId, String packageName, boolean active) {
// If already at specified state, then no-op.
if (active == association.isNotifyOnDeviceNearby()) {
if (DEBUG) Log.d(TAG, "Device presence listener is already at desired state.");
@@ -1025,9 +1201,9 @@
if (mDevicePresenceMonitor.isBlePresent(associationId)
|| mDevicePresenceMonitor.isSimulatePresent(associationId)) {
onDeviceAppearedInternal(associationId);
- onDeviceEventInternal(associationId, DEVICE_EVENT_BLE_APPEARED);
+ onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED);
} else if (mDevicePresenceMonitor.isBtConnected(associationId)) {
- onDeviceEventInternal(associationId, DEVICE_EVENT_BT_CONNECTED);
+ onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED);
}
}
@@ -1518,20 +1694,25 @@
private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
new CompanionDevicePresenceMonitor.Callback() {
- @Override
- public void onDeviceAppeared(int associationId) {
- onDeviceAppearedInternal(associationId);
- }
+ @Override
+ public void onDeviceAppeared(int associationId) {
+ onDeviceAppearedInternal(associationId);
+ }
- @Override
- public void onDeviceDisappeared(int associationId) {
- onDeviceDisappearedInternal(associationId);
- }
+ @Override
+ public void onDeviceDisappeared(int associationId) {
+ onDeviceDisappearedInternal(associationId);
+ }
- @Override
- public void onDeviceEvent(int associationId, int event) {
- onDeviceEventInternal(associationId, event);
- }
+ @Override
+ public void onDevicePresenceEvent(int associationId, int event) {
+ onDevicePresenceEventInternal(associationId, event);
+ }
+
+ @Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+ onDevicePresenceEventByUuidInternal(uuid, event);
+ }
};
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index 928842c..5abdb42 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -26,6 +26,7 @@
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.CompanionDeviceService;
+import android.companion.DevicePresenceEvent;
import android.companion.ICompanionDeviceService;
import android.content.ComponentName;
import android.content.Context;
@@ -106,12 +107,11 @@
void postOnDeviceDisappeared(@NonNull AssociationInfo associationInfo) {
post(companionService -> companionService.onDeviceDisappeared(associationInfo));
}
- void postOnDeviceEvent(@NonNull AssociationInfo associationInfo, int event) {
- post(companionService -> companionService.onDeviceEvent(associationInfo, event));
+
+ void postOnDevicePresenceEvent(@NonNull DevicePresenceEvent event) {
+ post(companionService -> companionService.onDevicePresenceEvent(event));
}
-
-
/**
* Post "unbind" job, which will run *after* all previously posted jobs complete.
*
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index e5a8c4f..5663434 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -27,6 +27,7 @@
import android.companion.datatransfer.PermissionSyncRequest;
import android.net.MacAddress;
import android.os.Binder;
+import android.os.ParcelUuid;
import android.os.ShellCommand;
import android.util.Base64;
import android.util.proto.ProtoOutputStream;
@@ -80,6 +81,19 @@
mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
return 0;
}
+
+ if ("simulate-device-uuid-event".equals(cmd) && Flags.devicePresence()) {
+ String uuid = getNextArgRequired();
+ String packageName = getNextArgRequired();
+ int userId = getNextIntArgRequired();
+ int event = getNextIntArgRequired();
+ ObservableUuid observableUuid = new ObservableUuid(
+ userId, ParcelUuid.fromString(uuid), packageName,
+ System.currentTimeMillis());
+ mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
+ return 0;
+ }
+
switch (cmd) {
case "list": {
final int userId = getNextIntArgRequired();
@@ -447,6 +461,16 @@
pw.println(" Case(3): ");
pw.println(" Make CDM act as if the given companion device is BT disconnected ");
pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
+ pw.println(" simulate-device-uuid-event UUID PACKAGE USERID EVENT");
+ pw.println(" Simulate the companion device event changes:");
+ pw.println(" Case(2): ");
+ pw.println(" Make CDM act as if the given DEVICE is BT connected base"
+ + "on the UUID");
+ pw.println(" Case(3): ");
+ pw.println(" Make CDM act as if the given DEVICE is BT disconnected base"
+ + "on the UUID");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
}
pw.println(" remove-inactive-associations");
diff --git a/services/companion/java/com/android/server/companion/ObservableUuid.java b/services/companion/java/com/android/server/companion/ObservableUuid.java
new file mode 100644
index 0000000..6ab3188
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/ObservableUuid.java
@@ -0,0 +1,54 @@
+/*
+ * 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.server.companion;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.ParcelUuid;
+
+public class ObservableUuid {
+ private final int mUserId;
+ private final String mPackageName;
+
+ private final ParcelUuid mUuid;
+
+ private final long mTimeApprovedMs;
+
+ public ObservableUuid(@UserIdInt int userId, @NonNull ParcelUuid uuid,
+ @NonNull String packageName, Long timeApprovedMs) {
+ mUserId = userId;
+ mUuid = uuid;
+ mPackageName = packageName;
+ mTimeApprovedMs = timeApprovedMs;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ParcelUuid getUuid() {
+ return mUuid;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public long getTimeApprovedMs() {
+ return mTimeApprovedMs;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/ObservableUuidStore.java
new file mode 100644
index 0000000..94be22a
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/ObservableUuidStore.java
@@ -0,0 +1,295 @@
+/*
+ * 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.server.companion;
+
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.isEndOfTag;
+import static com.android.server.companion.DataStoreUtils.isStartOfTag;
+import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.ParcelUuid;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class ObservableUuidStore {
+ private static final String TAG = "CDM_ObservableUuidStore";
+ private static final String FILE_NAME = "observing_uuids_presence.xml";
+ private static final String XML_TAG_UUIDS = "uuids";
+ private static final String XML_TAG_UUID = "uuid";
+ private static final String XML_ATTR_UUID = "uuid";
+ private static final String XML_ATTR_TIME_APPROVED = "time_approved";
+ private static final String XML_ATTR_USER_ID = "user_id";
+ private static final String XML_ATTR_PACKAGE = "package_name";
+ private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds
+
+
+ private final ExecutorService mExecutor;
+ private final ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
+ new ConcurrentHashMap<>();
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final SparseArray<List<ObservableUuid>> mCachedPerUser =
+ new SparseArray<>();
+
+ public ObservableUuidStore() {
+ mExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ /**
+ * Remove the observable uuid from the disk.
+ */
+ void removeObservableUuid(@UserIdInt int userId, ParcelUuid uuid, String packageName) {
+ List<ObservableUuid> cachedObservableUuids;
+
+ synchronized (mLock) {
+ // Remove requests from cache
+ cachedObservableUuids = readObservableUuidsFromCache(userId);
+ cachedObservableUuids.removeIf(
+ uuid1 -> uuid1.getPackageName().equals(packageName)
+ && uuid1.getUuid().equals(uuid));
+ mCachedPerUser.set(userId, cachedObservableUuids);
+ }
+ // Remove requests from store
+ mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
+ }
+
+ void writeObservableUuid(@UserIdInt int userId, ObservableUuid uuid) {
+ Slog.i(TAG, "Writing uuid=" + uuid.getUuid() + " to store.");
+
+ List<ObservableUuid> cachedObservableUuids;
+ synchronized (mLock) {
+ // Write to cache
+ cachedObservableUuids = readObservableUuidsFromCache(userId);
+ cachedObservableUuids.removeIf(uuid1 -> uuid1.getUuid().equals(
+ uuid.getUuid()) && uuid1.getPackageName().equals(uuid.getPackageName()));
+ cachedObservableUuids.add(uuid);
+ mCachedPerUser.set(userId, cachedObservableUuids);
+ }
+ // Write to store
+ mExecutor.execute(() -> writeObservableUuidToStore(userId, cachedObservableUuids));
+ }
+
+ private void writeObservableUuidToStore(@UserIdInt int userId,
+ @NonNull List<ObservableUuid> cachedObservableUuids) {
+ final AtomicFile file = getStorageFileForUser(userId);
+ Slog.i(TAG, "Writing ObservableUuid for user " + userId + " to file="
+ + file.getBaseFile().getPath());
+
+ // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+ // accesses to the file on the file system using this AtomicFile object.
+ synchronized (file) {
+ writeToFileSafely(file, out -> {
+ final TypedXmlSerializer serializer = Xml.resolveSerializer(out);
+ serializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startDocument(null, true);
+ writeObservableUuidToXml(serializer, cachedObservableUuids);
+ serializer.endDocument();
+ });
+ }
+ }
+
+ private void writeObservableUuidToXml(@NonNull TypedXmlSerializer serializer,
+ @Nullable Collection<ObservableUuid> uuids) throws IOException {
+ serializer.startTag(null, XML_TAG_UUIDS);
+
+ for (ObservableUuid uuid : uuids) {
+ writeUuidToXml(serializer, uuid);
+ }
+
+ serializer.endTag(null, XML_TAG_UUIDS);
+ }
+
+ private void writeUuidToXml(@NonNull TypedXmlSerializer serializer,
+ @NonNull ObservableUuid uuid) throws IOException {
+ serializer.startTag(null, XML_TAG_UUID);
+
+ writeIntAttribute(serializer, XML_ATTR_USER_ID, uuid.getUserId());
+ writeStringAttribute(serializer, XML_ATTR_UUID, uuid.getUuid().toString());
+ writeStringAttribute(serializer, XML_ATTR_PACKAGE, uuid.getPackageName());
+ writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, uuid.getTimeApprovedMs());
+
+ serializer.endTag(null, XML_TAG_UUID);
+ }
+
+ /**
+ * Read the observable UUIDs from the cache.
+ */
+ @GuardedBy("mLock")
+ private List<ObservableUuid> readObservableUuidsFromCache(@UserIdInt int userId) {
+ List<ObservableUuid> cachedObservableUuids = mCachedPerUser.get(userId);
+ if (cachedObservableUuids == null) {
+ Future<List<ObservableUuid>> future =
+ mExecutor.submit(() -> readObservableUuidFromStore(userId));
+ try {
+ cachedObservableUuids = future.get(READ_FROM_DISK_TIMEOUT, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "Thread reading ObservableUuid from disk is "
+ + "interrupted.");
+ } catch (ExecutionException e) {
+ Slog.e(TAG, "Error occurred while reading ObservableUuid "
+ + "from disk.");
+ } catch (TimeoutException e) {
+ Slog.e(TAG, "Reading ObservableUuid from disk timed out.");
+ }
+ mCachedPerUser.set(userId, cachedObservableUuids);
+ }
+ return cachedObservableUuids;
+ }
+
+ /**
+ * Reads previously persisted data for the given user
+ *
+ * @param userId Android UserID
+ * @return a list of ObservableUuid
+ */
+ @NonNull
+ public List<ObservableUuid> readObservableUuidFromStore(@UserIdInt int userId) {
+ final AtomicFile file = getStorageFileForUser(userId);
+ Slog.i(TAG, "Reading ObservableUuid for user " + userId + " from "
+ + "file=" + file.getBaseFile().getPath());
+
+ // getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
+ // accesses to the file on the file system using this AtomicFile object.
+ synchronized (file) {
+ if (!file.getBaseFile().exists()) {
+ Slog.d(TAG, "File does not exist -> Abort");
+ return new ArrayList<>();
+ }
+ try (FileInputStream in = file.openRead()) {
+ final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+ XmlUtils.beginDocument(parser, XML_TAG_UUIDS);
+
+ return readObservableUuidFromXml(parser);
+ } catch (XmlPullParserException | IOException e) {
+ Slog.e(TAG, "Error while reading requests file", e);
+ return new ArrayList<>();
+ }
+ }
+ }
+
+ @NonNull
+ private List<ObservableUuid> readObservableUuidFromXml(
+ @NonNull TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+ if (!isStartOfTag(parser, XML_TAG_UUIDS)) {
+ throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_UUIDS);
+ }
+
+ List<ObservableUuid> observableUuids = new ArrayList<>();
+
+ while (true) {
+ parser.nextTag();
+ if (isEndOfTag(parser, XML_TAG_UUIDS)) {
+ break;
+ }
+ if (isStartOfTag(parser, XML_TAG_UUID)) {
+ observableUuids.add(readUuidFromXml(parser));
+ }
+ }
+
+ return observableUuids;
+ }
+
+ private ObservableUuid readUuidFromXml(@NonNull TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ if (!isStartOfTag(parser, XML_TAG_UUID)) {
+ throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_UUID);
+ }
+
+ final int userId = readIntAttribute(parser, XML_ATTR_USER_ID);
+ final ParcelUuid uuid = ParcelUuid.fromString(readStringAttribute(parser, XML_ATTR_UUID));
+ final String packageName = readStringAttribute(parser, XML_ATTR_PACKAGE);
+ final Long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED);
+
+ return new ObservableUuid(userId, uuid, packageName, timeApproved);
+ }
+
+ /**
+ * Creates and caches {@link AtomicFile} object that represents the back-up file for the given
+ * user.
+ * <p>
+ * IMPORTANT: the method will ALWAYS return the same {@link AtomicFile} object, which makes it
+ * possible to synchronize reads and writes to the file using the returned object.
+ */
+ @NonNull
+ private AtomicFile getStorageFileForUser(@UserIdInt int userId) {
+ return mUserIdToStorageFile.computeIfAbsent(userId,
+ u -> createStorageFileForUser(userId, FILE_NAME));
+ }
+
+ /**
+ * @return A list of ObservableUuids per package.
+ */
+ public List<ObservableUuid> getObservableUuidsForPackage(
+ @UserIdInt int userId, @NonNull String packageName) {
+ final List<ObservableUuid> uuidsTobeObservedPerPackage = new ArrayList<>();
+ synchronized (mLock) {
+ final List<ObservableUuid> uuids = readObservableUuidsFromCache(userId);
+
+ for (ObservableUuid uuid : uuids) {
+ if (uuid.getPackageName().equals(packageName)) {
+ uuidsTobeObservedPerPackage.add(uuid);
+ }
+ }
+ }
+
+ return uuidsTobeObservedPerPackage;
+ }
+
+ /**
+ * @return A list of ObservableUuids per user.
+ */
+ public List<ObservableUuid> getObservableUuidsForUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ return readObservableUuidsFromCache(userId);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java
index f4e14df..15bebba 100644
--- a/services/companion/java/com/android/server/companion/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
+import static android.Manifest.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -174,6 +175,14 @@
+ " for u" + userId + "/" + packageName);
}
+ static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+ if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ + "permissions to request observing device presence base on the UUID");
+ }
+ }
+
/**
* Check if the caller is either:
* <ul>
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 6ba85bd..7eca119 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -16,6 +16,9 @@
package com.android.server.companion.presence;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+
import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.presence.Utils.btDeviceToString;
@@ -27,6 +30,7 @@
import android.net.MacAddress;
import android.os.Handler;
import android.os.HandlerExecutor;
+import android.os.ParcelUuid;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -35,8 +39,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.companion.AssociationStore;
+import com.android.server.companion.ObservableUuid;
+import com.android.server.companion.ObservableUuidStore;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -53,6 +60,8 @@
void onBluetoothCompanionDeviceConnected(int associationId);
void onBluetoothCompanionDeviceDisconnected(int associationId);
+
+ void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
}
private final UserManager mUserManager;
@@ -61,6 +70,8 @@
/** A set of ALL connected BT device (not only companion.) */
private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
+
/**
* A structure hold the connected BT devices that are pending to be reported to the companion
* app when the user unlocks the local device per userId.
@@ -70,8 +81,10 @@
final SparseArray<Set<BluetoothDevice>> mPendingConnectedDevices = new SparseArray<>();
BluetoothCompanionDeviceConnectionListener(UserManager userManager,
- @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
mCallback = callback;
mUserManager = userManager;
}
@@ -109,7 +122,6 @@
bluetoothDevices.add(device);
mPendingConnectedDevices.put(userId, bluetoothDevices);
}
-
} else {
onDeviceConnectivityChanged(device, true);
}
@@ -155,8 +167,13 @@
}
private void onDeviceConnectivityChanged(@NonNull BluetoothDevice device, boolean connected) {
+ int userId = UserHandle.myUserId();
final List<AssociationInfo> associations =
mAssociationStore.getAssociationsByAddress(device.getAddress());
+ final List<ObservableUuid> observableUuids =
+ mObservableUuidStore.getObservableUuidsForUser(userId);
+ final List<ParcelUuid> deviceUuids = device.getUuids() == null
+ ? Collections.emptyList() : Arrays.asList(device.getUuids());
if (DEBUG) {
Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
@@ -177,6 +194,14 @@
mCallback.onBluetoothCompanionDeviceDisconnected(id);
}
}
+
+ for (ObservableUuid uuid : observableUuids) {
+ if (deviceUuids.contains(uuid.getUuid())) {
+ mCallback.onDevicePresenceEventByUuid(
+ uuid, connected ? EVENT_BT_CONNECTED
+ : EVENT_BT_DISCONNECTED);
+ }
+ }
}
@Override
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index e42b935..54a4692 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -16,12 +16,12 @@
package com.android.server.companion.presence;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BLE_DISAPPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_CONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_BT_DISCONNECTED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_APPEARED;
-import static android.companion.CompanionDeviceService.DEVICE_EVENT_SELF_MANAGED_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
+import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
@@ -36,12 +36,15 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelUuid;
import android.os.UserManager;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.server.companion.AssociationStore;
+import com.android.server.companion.ObservableUuid;
+import com.android.server.companion.ObservableUuidStore;
import java.io.PrintWriter;
import java.util.HashSet;
@@ -61,7 +64,7 @@
* <li> {@link #isDevicePresent(int)}
* <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
* <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
- * <li> {@link Callback#onDeviceStateChanged(int, int)}}
+ * <li> {@link Callback#onDevicePresenceEvent(int, int)}}
* </ul>
*/
@SuppressLint("LongLogTag")
@@ -78,11 +81,15 @@
/** Invoked when a companion device no longer seen nearby or disconnects. */
void onDeviceDisappeared(int associationId);
- /**Invoked when device has corresponding event changes. */
- void onDeviceEvent(int associationId, int event);
+ /** Invoked when device has corresponding event changes. */
+ void onDevicePresenceEvent(int associationId, int event);
+
+ /** Invoked when device has corresponding event changes base on the UUID */
+ void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
}
private final @NonNull AssociationStore mAssociationStore;
+ private final @NonNull ObservableUuidStore mObservableUuidStore;
private final @NonNull Callback mCallback;
private final @NonNull BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
private final @NonNull BleCompanionDeviceScanner mBleScanner;
@@ -94,6 +101,7 @@
private final @NonNull Set<Integer> mConnectedBtDevices = new HashSet<>();
private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ private final @NonNull Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
// Tracking "simulated" presence. Used for debugging and testing only.
private final @NonNull Set<Integer> mSimulated = new HashSet<>();
@@ -101,11 +109,14 @@
new SimulatedDevicePresenceSchedulerHelper();
public CompanionDevicePresenceMonitor(UserManager userManager,
- @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
mCallback = callback;
mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ associationStore, mObservableUuidStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
mBleScanner = new BleCompanionDeviceScanner(associationStore,
/* BleCompanionDeviceScanner.Callback */ this);
}
@@ -126,6 +137,20 @@
}
/**
+ * @return current connected UUID devices.
+ */
+ public Set<ParcelUuid> getCurrentConnectedUuidDevices() {
+ return mConnectedUuidDevices;
+ }
+
+ /**
+ * Remove current connected UUID device.
+ */
+ public void removeCurrentConnectedUuidDevice(ParcelUuid uuid) {
+ mConnectedUuidDevices.remove(uuid);
+ }
+
+ /**
* @return whether the associated companion devices is present. I.e. device is nearby (for BLE);
* or devices is connected (for Bluetooth); or reported (by the application) to be
* nearby (for "self-managed" associations).
@@ -138,6 +163,13 @@
}
/**
+ * @return whether the current uuid to be observed is present.
+ */
+ public boolean isDeviceUuidPresent(ParcelUuid uuid) {
+ return mConnectedUuidDevices.contains(uuid);
+ }
+
+ /**
* @return whether the current device is BT connected and had already reported to the app.
*/
@@ -169,8 +201,8 @@
* {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
*/
public void onSelfManagedDeviceConnected(int associationId) {
- onDeviceEvent(mReportedSelfManagedDevices,
- associationId, DEVICE_EVENT_SELF_MANAGED_APPEARED);
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_APPEARED);
}
/**
@@ -183,23 +215,23 @@
* {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
*/
public void onSelfManagedDeviceDisconnected(int associationId) {
- onDeviceEvent(mReportedSelfManagedDevices,
- associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED);
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
/**
* Marks a "self-managed" device as disconnected when binderDied.
*/
public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDeviceEvent(mReportedSelfManagedDevices,
- associationId, DEVICE_EVENT_SELF_MANAGED_DISAPPEARED);
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
@Override
public void onBluetoothCompanionDeviceConnected(int associationId) {
Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+ "associationId( " + associationId + " )");
- onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_CONNECTED);
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
// Stop scanning for BLE devices when this device is connected
// and there are no other devices to connect to.
if (canStopBleScan()) {
@@ -214,22 +246,53 @@
// Start BLE scanning when the device is disconnected.
mBleScanner.startScan();
- onDeviceEvent(mConnectedBtDevices, associationId, DEVICE_EVENT_BT_DISCONNECTED);
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
}
@Override
+ public void onDevicePresenceEventByUuid(ObservableUuid uuid, int event) {
+ final ParcelUuid parcelUuid = uuid.getUuid();
+
+ switch(event) {
+ case EVENT_BT_CONNECTED:
+ boolean added = mConnectedUuidDevices.add(parcelUuid);
+
+ if (!added) {
+ Slog.w(TAG, "Uuid= " + parcelUuid + "is ALREADY reported as "
+ + "present by this event=" + event);
+ }
+
+ break;
+ case EVENT_BT_DISCONNECTED:
+ final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
+
+ if (!removed) {
+ Slog.w(TAG, "UUID= " + parcelUuid + " was NOT reported "
+ + "as present by this event= " + event);
+
+ return;
+ }
+
+ break;
+ }
+
+ mCallback.onDevicePresenceEventByUuid(uuid, event);
+ }
+
+
+ @Override
public void onBleCompanionDeviceFound(int associationId) {
- onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_APPEARED);
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
}
@Override
public void onBleCompanionDeviceLost(int associationId) {
- onDeviceEvent(mNearbyBleDevices, associationId, DEVICE_EVENT_BLE_DISAPPEARED);
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
}
/** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
@TestApi
- public void simulateDeviceEvent(int associationId, int state) {
+ public void simulateDeviceEvent(int associationId, int event) {
// IMPORTANT: this API should only be invoked via the
// 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
// make this call are SHELL and ROOT.
@@ -238,32 +301,43 @@
// Make sure the association exists.
enforceAssociationExists(associationId);
- switch (state) {
- case DEVICE_EVENT_BLE_APPEARED:
- simulateDeviceAppeared(associationId, state);
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ simulateDeviceAppeared(associationId, event);
break;
- case DEVICE_EVENT_BT_CONNECTED:
+ case EVENT_BT_CONNECTED:
onBluetoothCompanionDeviceConnected(associationId);
break;
- case DEVICE_EVENT_BLE_DISAPPEARED:
- simulateDeviceDisappeared(associationId, state);
+ case EVENT_BLE_DISAPPEARED:
+ simulateDeviceDisappeared(associationId, event);
break;
- case DEVICE_EVENT_BT_DISCONNECTED:
+ case EVENT_BT_DISCONNECTED:
onBluetoothCompanionDeviceDisconnected(associationId);
break;
default:
- throw new IllegalArgumentException("State: " + state + "is not supported");
+ throw new IllegalArgumentException("Event: " + event + "is not supported");
}
}
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ public void simulateDeviceEventByUuid(ObservableUuid uuid, int event) {
+ // IMPORTANT: this API should only be invoked via the
+ // 'companiondevice simulate-device-uuid-events' Shell command, so the only uid-s allowed to
+ // make this call are SHELL and ROOT.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ onDevicePresenceEventByUuid(uuid, event);
+ }
+
private void simulateDeviceAppeared(int associationId, int state) {
- onDeviceEvent(mSimulated, associationId, state);
+ onDevicePresenceEvent(mSimulated, associationId, state);
mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
}
private void simulateDeviceDisappeared(int associationId, int state) {
mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- onDeviceEvent(mSimulated, associationId, state);
+ onDevicePresenceEvent(mSimulated, associationId, state);
}
private void enforceAssociationExists(int associationId) {
@@ -273,14 +347,14 @@
}
}
- private void onDeviceEvent(@NonNull Set<Integer> presentDevicesForSource,
+ private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
int associationId, int event) {
- Slog.i(TAG, "onDeviceEvent() id=" + associationId + ", state=" + event);
+ Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event);
switch (event) {
- case DEVICE_EVENT_BLE_APPEARED:
- case DEVICE_EVENT_BT_CONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_APPEARED:
+ case EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
final boolean added = presentDevicesForSource.add(associationId);
if (!added) {
@@ -292,9 +366,9 @@
mCallback.onDeviceAppeared(associationId);
break;
- case DEVICE_EVENT_BLE_DISAPPEARED:
- case DEVICE_EVENT_BT_DISCONNECTED:
- case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED:
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
final boolean removed = presentDevicesForSource.remove(associationId);
if (!removed) {
@@ -312,7 +386,7 @@
return;
}
- mCallback.onDeviceEvent(associationId, event);
+ mCallback.onDevicePresenceEvent(associationId, event);
}
/**
@@ -436,7 +510,7 @@
public void handleMessage(@NonNull Message msg) {
final int associationId = msg.what;
if (mSimulated.contains(associationId)) {
- onDeviceEvent(mSimulated, associationId, DEVICE_EVENT_BLE_DISAPPEARED);
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
}
}
}