Loading core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -10276,6 +10276,7 @@ package android.companion { 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 @FlaggedApi("android.companion.notify_association_removed") public static final int EVENT_ASSOCIATION_REMOVED = 6; // 0x6 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 core/java/android/companion/DevicePresenceEvent.java +10 −1 Original line number Diff line number Diff line Loading @@ -45,7 +45,8 @@ public final class DevicePresenceEvent implements Parcelable { EVENT_BT_CONNECTED, EVENT_BT_DISCONNECTED, EVENT_SELF_MANAGED_APPEARED, EVENT_SELF_MANAGED_DISAPPEARED EVENT_SELF_MANAGED_DISAPPEARED, EVENT_ASSOCIATION_REMOVED }) @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -98,6 +99,14 @@ public final class DevicePresenceEvent implements Parcelable { * that a device has disappeared on its own. */ public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5; /** * A companion app will receives the callback * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} * with this event when the {@link AssociationInfo} is removed. */ @FlaggedApi(Flags.FLAG_NOTIFY_ASSOCIATION_REMOVED) public static final int EVENT_ASSOCIATION_REMOVED = 6; private final int mAssociationId; private final int mEvent; @Nullable Loading core/java/android/companion/flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -80,3 +80,11 @@ flag { description: "Enable association verification APIs" bug: "371198526" } flag { name: "notify_association_removed" is_exported: true namespace: "companion" description: "Enable association removed event API" bug: "398042032" } services/companion/java/com/android/server/companion/association/DisassociationProcessor.java +15 −12 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.NotificationManager; import android.companion.AssociationInfo; import android.companion.Flags; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManagerInternal; Loading Loading @@ -167,7 +168,8 @@ public class DisassociationProcessor { removeRoleHolderForAssociation(mContext, association.getUserId(), association.getPackageName(), association.getDeviceProfile()); } // Handle unbind in DevicePresenceProcessor instead. if (!Flags.notifyAssociationRemoved()) { // Unbind the app if needed. final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(id); if (!wasPresent || !association.isNotifyOnDeviceNearby()) { Loading @@ -181,6 +183,7 @@ public class DisassociationProcessor { mCompanionAppController.unbindCompanionApp(userId, packageName); } } } /** * @deprecated Use {@link #disassociate(int, String)} instead. Loading services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java +52 −7 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.companion.devicepresence; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.companion.DevicePresenceEvent.EVENT_ASSOCIATION_REMOVED; import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED; import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; Loading Loading @@ -44,6 +45,7 @@ import android.companion.AssociationInfo; import android.companion.DeviceId; import android.companion.DeviceNotAssociatedException; import android.companion.DevicePresenceEvent; import android.companion.Flags; import android.companion.ObservingDevicePresenceRequest; import android.content.Context; import android.hardware.power.Mode; Loading Loading @@ -257,10 +259,12 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Device already present, trigger the callback immediately. if (associationInfo.shouldBindWhenPresent()) { if (isBlePresent(associationInfo.getId())) { bindAppAndNotify(EVENT_BLE_APPEARED, associationInfo, callingPackage, userId); notifyAndExemptApp( EVENT_BLE_APPEARED, associationInfo, callingPackage, userId); } if (isBtConnected(associationInfo.getId())) { bindAppAndNotify(EVENT_BT_CONNECTED, associationInfo, callingPackage, userId); notifyAndExemptApp( EVENT_BT_CONNECTED, associationInfo, callingPackage, userId); } } } else { Loading Loading @@ -444,6 +448,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene mAssociationStore.getActiveAssociationsByPackage(userId, packageName); final List<ObservableUuid> observableUuids = mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); final List<AssociationInfo> associationInfos = mAssociationStore.getActiveAssociationsByUser(userId); for (AssociationInfo association : packageAssociations) { if (!association.shouldBindWhenPresent()) continue; Loading @@ -456,6 +462,14 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } } for (AssociationInfo ai : associationInfos) { List<String> packagesToNotify = ai.getPackagesToNotify(); if (packagesToNotify != null && packagesToNotify.contains(packageName) && isDevicePresent(ai.getId())) { return true; } } return false; } Loading Loading @@ -787,7 +801,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Also send the callback to the package that registered to be notified. if (association.getPackagesToNotify() != null) { for (String packageToNotify : association.getPackagesToNotify()) { bindAppAndNotify(eventType, association, packageToNotify, userId); notifyAndExemptApp(eventType, association, packageToNotify, userId); } } } Loading Loading @@ -816,7 +830,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene userId, packageToNotify, deviceProfile, event); // Also unbind the package that registered to be notified. if (!shouldBindPackage(userId, packageToNotify)) { unbindAndNotExemptPackage(userId, packageToNotify); unbindAndRemoveExemptionForApp(userId, packageToNotify); } } } Loading @@ -824,7 +838,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Check if there are other devices associated to the app that are present. if (!shouldBindPackage(userId, packageName)) { unbindAndNotExemptPackage(userId, packageName); unbindAndRemoveExemptionForApp(userId, packageName); } break; default: Loading Loading @@ -1053,6 +1067,24 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene public void onAssociationRemoved(@NonNull AssociationInfo association) { final int id = association.getId(); if (association.isNotifyOnDeviceNearby() && Flags.notifyAssociationRemoved()) { final int userId = association.getUserId(); final String packageName = association.getPackageName(); final DevicePresenceEvent event = new DevicePresenceEvent( id, EVENT_ASSOCIATION_REMOVED, /* uuid */ null); // Notify and unbind for the original package that created this association. processNotifyAssociationRemoved(userId, packageName, event, association.getDeviceProfile(), association.isSelfManaged()); // Notify and unbind for non companion app. if (Flags.associationVerification() && association.getPackagesToNotify() != null) { for (String packageToNotify : association.getPackagesToNotify()) { if (!packageToNotify.equals(packageName)) { processNotifyAssociationRemoved(userId, packageToNotify, event, association.getDeviceProfile(), association.isSelfManaged()); } } } } mConnectedBtDevices.remove(id); mNearbyBleDevices.remove(id); mConnectedSelfManagedDevices.remove(id); Loading Loading @@ -1330,7 +1362,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } } private void bindAppAndNotify( private void notifyAndExemptApp( int eventType, AssociationInfo associationInfo, String packageName, int userId) { final DevicePresenceEvent event = new DevicePresenceEvent( associationInfo.getId(), eventType, null); Loading @@ -1340,8 +1372,21 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene notifyDevicePresenceEvent(userId, packageName, associationInfo.getDeviceProfile(), event); } private void unbindAndNotExemptPackage(int userId, String packageName) { private void unbindAndRemoveExemptionForApp(int userId, String packageName) { mCompanionAppBinder.unbindCompanionApp(userId, packageName); mCompanionExemptionProcessor.exemptPackage(userId, packageName, false); } private void processNotifyAssociationRemoved(int userId, @NonNull String packageName, @NonNull DevicePresenceEvent event, String deviceProfile, boolean isSelfManaged) { bindApplicationIfNeeded(userId, packageName, isSelfManaged); notifyDevicePresenceEvent(userId, packageName, deviceProfile, event); if (!shouldBindPackage(userId, packageName)) { mCompanionAppBinder.unbindCompanionApp(userId, packageName); } else { Slog.i(TAG, "Not unbinding package " + packageName + " as other associations are still present."); } } } Loading
core/api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -10276,6 +10276,7 @@ package android.companion { 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 @FlaggedApi("android.companion.notify_association_removed") public static final int EVENT_ASSOCIATION_REMOVED = 6; // 0x6 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
core/java/android/companion/DevicePresenceEvent.java +10 −1 Original line number Diff line number Diff line Loading @@ -45,7 +45,8 @@ public final class DevicePresenceEvent implements Parcelable { EVENT_BT_CONNECTED, EVENT_BT_DISCONNECTED, EVENT_SELF_MANAGED_APPEARED, EVENT_SELF_MANAGED_DISAPPEARED EVENT_SELF_MANAGED_DISAPPEARED, EVENT_ASSOCIATION_REMOVED }) @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -98,6 +99,14 @@ public final class DevicePresenceEvent implements Parcelable { * that a device has disappeared on its own. */ public static final int EVENT_SELF_MANAGED_DISAPPEARED = 5; /** * A companion app will receives the callback * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} * with this event when the {@link AssociationInfo} is removed. */ @FlaggedApi(Flags.FLAG_NOTIFY_ASSOCIATION_REMOVED) public static final int EVENT_ASSOCIATION_REMOVED = 6; private final int mAssociationId; private final int mEvent; @Nullable Loading
core/java/android/companion/flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -80,3 +80,11 @@ flag { description: "Enable association verification APIs" bug: "371198526" } flag { name: "notify_association_removed" is_exported: true namespace: "companion" description: "Enable association removed event API" bug: "398042032" }
services/companion/java/com/android/server/companion/association/DisassociationProcessor.java +15 −12 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.NotificationManager; import android.companion.AssociationInfo; import android.companion.Flags; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManagerInternal; Loading Loading @@ -167,7 +168,8 @@ public class DisassociationProcessor { removeRoleHolderForAssociation(mContext, association.getUserId(), association.getPackageName(), association.getDeviceProfile()); } // Handle unbind in DevicePresenceProcessor instead. if (!Flags.notifyAssociationRemoved()) { // Unbind the app if needed. final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(id); if (!wasPresent || !association.isNotifyOnDeviceNearby()) { Loading @@ -181,6 +183,7 @@ public class DisassociationProcessor { mCompanionAppController.unbindCompanionApp(userId, packageName); } } } /** * @deprecated Use {@link #disassociate(int, String)} instead. Loading
services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java +52 −7 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.companion.devicepresence; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.companion.DevicePresenceEvent.EVENT_ASSOCIATION_REMOVED; import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; import static android.companion.DevicePresenceEvent.EVENT_BLE_DISAPPEARED; import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; Loading Loading @@ -44,6 +45,7 @@ import android.companion.AssociationInfo; import android.companion.DeviceId; import android.companion.DeviceNotAssociatedException; import android.companion.DevicePresenceEvent; import android.companion.Flags; import android.companion.ObservingDevicePresenceRequest; import android.content.Context; import android.hardware.power.Mode; Loading Loading @@ -257,10 +259,12 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Device already present, trigger the callback immediately. if (associationInfo.shouldBindWhenPresent()) { if (isBlePresent(associationInfo.getId())) { bindAppAndNotify(EVENT_BLE_APPEARED, associationInfo, callingPackage, userId); notifyAndExemptApp( EVENT_BLE_APPEARED, associationInfo, callingPackage, userId); } if (isBtConnected(associationInfo.getId())) { bindAppAndNotify(EVENT_BT_CONNECTED, associationInfo, callingPackage, userId); notifyAndExemptApp( EVENT_BT_CONNECTED, associationInfo, callingPackage, userId); } } } else { Loading Loading @@ -444,6 +448,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene mAssociationStore.getActiveAssociationsByPackage(userId, packageName); final List<ObservableUuid> observableUuids = mObservableUuidStore.getObservableUuidsForPackage(userId, packageName); final List<AssociationInfo> associationInfos = mAssociationStore.getActiveAssociationsByUser(userId); for (AssociationInfo association : packageAssociations) { if (!association.shouldBindWhenPresent()) continue; Loading @@ -456,6 +462,14 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } } for (AssociationInfo ai : associationInfos) { List<String> packagesToNotify = ai.getPackagesToNotify(); if (packagesToNotify != null && packagesToNotify.contains(packageName) && isDevicePresent(ai.getId())) { return true; } } return false; } Loading Loading @@ -787,7 +801,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Also send the callback to the package that registered to be notified. if (association.getPackagesToNotify() != null) { for (String packageToNotify : association.getPackagesToNotify()) { bindAppAndNotify(eventType, association, packageToNotify, userId); notifyAndExemptApp(eventType, association, packageToNotify, userId); } } } Loading Loading @@ -816,7 +830,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene userId, packageToNotify, deviceProfile, event); // Also unbind the package that registered to be notified. if (!shouldBindPackage(userId, packageToNotify)) { unbindAndNotExemptPackage(userId, packageToNotify); unbindAndRemoveExemptionForApp(userId, packageToNotify); } } } Loading @@ -824,7 +838,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Check if there are other devices associated to the app that are present. if (!shouldBindPackage(userId, packageName)) { unbindAndNotExemptPackage(userId, packageName); unbindAndRemoveExemptionForApp(userId, packageName); } break; default: Loading Loading @@ -1053,6 +1067,24 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene public void onAssociationRemoved(@NonNull AssociationInfo association) { final int id = association.getId(); if (association.isNotifyOnDeviceNearby() && Flags.notifyAssociationRemoved()) { final int userId = association.getUserId(); final String packageName = association.getPackageName(); final DevicePresenceEvent event = new DevicePresenceEvent( id, EVENT_ASSOCIATION_REMOVED, /* uuid */ null); // Notify and unbind for the original package that created this association. processNotifyAssociationRemoved(userId, packageName, event, association.getDeviceProfile(), association.isSelfManaged()); // Notify and unbind for non companion app. if (Flags.associationVerification() && association.getPackagesToNotify() != null) { for (String packageToNotify : association.getPackagesToNotify()) { if (!packageToNotify.equals(packageName)) { processNotifyAssociationRemoved(userId, packageToNotify, event, association.getDeviceProfile(), association.isSelfManaged()); } } } } mConnectedBtDevices.remove(id); mNearbyBleDevices.remove(id); mConnectedSelfManagedDevices.remove(id); Loading Loading @@ -1330,7 +1362,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } } private void bindAppAndNotify( private void notifyAndExemptApp( int eventType, AssociationInfo associationInfo, String packageName, int userId) { final DevicePresenceEvent event = new DevicePresenceEvent( associationInfo.getId(), eventType, null); Loading @@ -1340,8 +1372,21 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene notifyDevicePresenceEvent(userId, packageName, associationInfo.getDeviceProfile(), event); } private void unbindAndNotExemptPackage(int userId, String packageName) { private void unbindAndRemoveExemptionForApp(int userId, String packageName) { mCompanionAppBinder.unbindCompanionApp(userId, packageName); mCompanionExemptionProcessor.exemptPackage(userId, packageName, false); } private void processNotifyAssociationRemoved(int userId, @NonNull String packageName, @NonNull DevicePresenceEvent event, String deviceProfile, boolean isSelfManaged) { bindApplicationIfNeeded(userId, packageName, isSelfManaged); notifyDevicePresenceEvent(userId, packageName, deviceProfile, event); if (!shouldBindPackage(userId, packageName)) { mCompanionAppBinder.unbindCompanionApp(userId, packageName); } else { Slog.i(TAG, "Not unbinding package " + packageName + " as other associations are still present."); } } }