summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Greg Kaiser <gkaiser@google.com> 2024-03-22 20:06:58 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-03-22 20:06:58 +0000
commit3ed90488cb97c55c098000016b83980070eaa7c4 (patch)
tree11482594bc69561f2323eb2505aaa75a1de32a59
parent170c34a68ac1f13c229bec046acc3ce87655f58e (diff)
parent8e73ebe7b1612cab4123b39d3fa38a6fc8a30632 (diff)
Merge "Revert "[CDM][Refactoring 4/N] Move device presence logic out of..."" into main
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java8
-rw-r--r--core/java/android/companion/ICompanionDeviceManager.aidl24
-rw-r--r--services/companion/java/com/android/server/companion/CompanionApplicationController.java567
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java549
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java5
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java (renamed from services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java)63
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java41
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java41
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationStore.java53
-rw-r--r--services/companion/java/com/android/server/companion/association/DisassociationProcessor.java49
-rw-r--r--services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java51
-rw-r--r--services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java70
-rw-r--r--services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java2
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java392
-rw-r--r--services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java620
-rw-r--r--services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java1042
-rw-r--r--services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java14
-rw-r--r--services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java29
-rw-r--r--services/companion/java/com/android/server/companion/utils/PermissionsUtils.java20
20 files changed, 1873 insertions, 1769 deletions
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 2c26389071ce..5e00b7a798d8 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1086,7 +1086,7 @@ public final class CompanionDeviceManager {
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.legacyStartObservingDevicePresence(deviceAddress,
+ mService.registerDevicePresenceListenerService(deviceAddress,
mContext.getOpPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1128,7 +1128,7 @@ public final class CompanionDeviceManager {
}
Objects.requireNonNull(deviceAddress, "address cannot be null");
try {
- mService.legacyStopObservingDevicePresence(deviceAddress,
+ mService.unregisterDevicePresenceListenerService(deviceAddress,
mContext.getPackageName(), mContext.getUserId());
} catch (RemoteException e) {
ExceptionUtils.propagateIfInstanceOf(e.getCause(), DeviceNotAssociatedException.class);
@@ -1328,7 +1328,7 @@ public final class CompanionDeviceManager {
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceAppeared(int associationId) {
try {
- mService.notifySelfManagedDeviceAppeared(associationId);
+ mService.notifyDeviceAppeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1350,7 +1350,7 @@ public final class CompanionDeviceManager {
@RequiresPermission(android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED)
public void notifyDeviceDisappeared(int associationId) {
try {
- mService.notifySelfManagedDeviceDisappeared(associationId);
+ mService.notifyDeviceDisappeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 1b00f90e1fb3..57d59e5e5bf0 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -59,16 +59,12 @@ interface ICompanionDeviceManager {
int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void legacyStartObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void legacyStopObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
-
- @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void startObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+ void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
+ int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
+ void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
+ int userId);
boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
@@ -97,11 +93,9 @@ interface ICompanionDeviceManager {
@EnforcePermission("USE_COMPANION_TRANSPORTS")
void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
- @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
- void notifySelfManagedDeviceAppeared(int associationId);
+ void notifyDeviceAppeared(int associationId);
- @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
- void notifySelfManagedDeviceDisappeared(int associationId);
+ void notifyDeviceDisappeared(int associationId);
PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
int associationId);
@@ -141,4 +135,10 @@ interface ICompanionDeviceManager {
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/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
new file mode 100644
index 000000000000..0a4148535451
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2022 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 android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+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.hardware.power.Mode;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.os.PowerManagerInternal;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.infra.PerUser;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.presence.ObservableUuidStore;
+import com.android.server.companion.utils.PackageUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Manages communication with companion applications via
+ * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
+ * the services, maintaining the connection (the binding), and invoking callback methods such as
+ * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
+ * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
+ * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
+ * application process.
+ *
+ * <p>
+ * The following is the list of the APIs provided by {@link CompanionApplicationController} (to be
+ * utilized by {@link CompanionDeviceManagerService}):
+ * <ul>
+ * <li> {@link #bindCompanionApplication(int, String, boolean)}
+ * <li> {@link #unbindCompanionApplication(int, String)}
+ * <li> {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)}
+ * <li> {@link #isCompanionApplicationBound(int, String)}
+ * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
+ * </ul>
+ *
+ * @see CompanionDeviceService
+ * @see android.companion.ICompanionDeviceService
+ * @see CompanionDeviceServiceConnector
+ */
+@SuppressLint("LongLogTag")
+public class CompanionApplicationController {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CDM_CompanionApplicationController";
+
+ private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
+
+ 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;
+
+ private final PowerManagerInternal mPowerManagerInternal;
+
+ @GuardedBy("mBoundCompanionApplications")
+ private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
+ mBoundCompanionApplications;
+ @GuardedBy("mScheduledForRebindingCompanionApplications")
+ private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
+
+ CompanionApplicationController(Context context, AssociationStore associationStore,
+ ObservableUuidStore observableUuidStore,
+ CompanionDevicePresenceMonitor companionDevicePresenceMonitor,
+ PowerManagerInternal powerManagerInternal) {
+ mContext = context;
+ mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
+ mDevicePresenceMonitor = companionDevicePresenceMonitor;
+ mPowerManagerInternal = powerManagerInternal;
+ mCompanionServicesRegister = new CompanionServicesRegister();
+ mBoundCompanionApplications = new AndroidPackageMap<>();
+ mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
+ }
+
+ void onPackagesChanged(@UserIdInt int userId) {
+ mCompanionServicesRegister.invalidate(userId);
+ }
+
+ /**
+ * CDM binds to the companion app.
+ */
+ public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
+ boolean isSelfManaged) {
+ if (DEBUG) {
+ Log.i(TAG, "bind() u" + userId + "/" + packageName
+ + " isSelfManaged=" + isSelfManaged);
+ }
+
+ final List<ComponentName> companionServices =
+ mCompanionServicesRegister.forPackage(userId, packageName);
+ if (companionServices.isEmpty()) {
+ Slog.w(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
+ + "eligible CompanionDeviceService not found.\n"
+ + "A CompanionDeviceService should declare an intent-filter for "
+ + "\"android.companion.CompanionDeviceService\" action and require "
+ + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
+ return;
+ }
+
+ final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>();
+ synchronized (mBoundCompanionApplications) {
+ if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
+ if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound.");
+ return;
+ }
+
+ for (int i = 0; i < companionServices.size(); i++) {
+ boolean isPrimary = i == 0;
+ serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId,
+ companionServices.get(i), isSelfManaged, isPrimary));
+ }
+
+ mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
+ }
+
+ // Set listeners for both Primary and Secondary connectors.
+ for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.setListener(this::onBinderDied);
+ }
+
+ // Now "bind" all the connectors: the primary one and the rest of them.
+ for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.connect();
+ }
+ }
+
+ /**
+ * CDM unbinds the companion app.
+ */
+ public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
+ if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);
+
+ final List<CompanionDeviceServiceConnector> serviceConnectors;
+
+ synchronized (mBoundCompanionApplications) {
+ serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
+ }
+
+ if (serviceConnectors == null) {
+ if (DEBUG) {
+ Log.e(TAG, "unbindCompanionApplication(): "
+ + "u" + userId + "/" + packageName + " is NOT bound");
+ Log.d(TAG, "Stacktrace", new Throwable());
+ }
+ return;
+ }
+
+ for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
+ serviceConnector.postUnbind();
+ }
+ }
+
+ /**
+ * @return whether the companion application is bound now.
+ */
+ public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mBoundCompanionApplications) {
+ return mBoundCompanionApplications.containsValueForPackage(userId, packageName);
+ }
+ }
+
+ private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
+ CompanionDeviceServiceConnector serviceConnector) {
+ Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
+
+ if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
+ if (DEBUG) {
+ Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
+ + serviceConnector.getComponentName());
+ }
+ return;
+ }
+
+ if (serviceConnector.isPrimary()) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.setValueForPackage(
+ userId, packageName, true);
+ }
+ }
+
+ // Rebinding in 10 seconds.
+ Handler.getMain().postDelayed(() ->
+ onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector),
+ REBIND_TIMEOUT);
+ }
+
+ private boolean isRebindingCompanionApplicationScheduled(
+ @UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ return mScheduledForRebindingCompanionApplications.containsValueForPackage(
+ userId, packageName);
+ }
+ }
+
+ private void onRebindingCompanionApplicationTimeout(
+ @UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionDeviceServiceConnector serviceConnector) {
+ // Re-mark the application is bound.
+ if (serviceConnector.isPrimary()) {
+ synchronized (mBoundCompanionApplications) {
+ if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
+ List<CompanionDeviceServiceConnector> serviceConnectors =
+ Collections.singletonList(serviceConnector);
+ mBoundCompanionApplications.setValueForPackage(userId, packageName,
+ serviceConnectors);
+ }
+ }
+
+ synchronized (mScheduledForRebindingCompanionApplications) {
+ mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
+ }
+ }
+
+ serviceConnector.connect();
+ }
+
+ /**
+ * Notify the app that the device appeared.
+ *
+ * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+ */
+ @Deprecated
+ public void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ Slog.i(TAG, "notifyDevice_Appeared() id=" + association.getId() + " u" + userId
+ + "/" + packageName);
+
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notify_CompanionApplicationDevice_Appeared(): "
+ + "u" + userId + "/" + packageName + " is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Log.i(TAG, "Calling onDeviceAppeared to userId=[" + userId + "] package=["
+ + packageName + "] associationId=[" + association.getId() + "]");
+
+ primaryServiceConnector.postOnDeviceAppeared(association);
+ }
+
+ /**
+ * Notify the app that the device disappeared.
+ *
+ * @deprecated use {@link #notifyCompanionDevicePresenceEvent(AssociationInfo, int)} instead
+ */
+ @Deprecated
+ public void notifyCompanionApplicationDeviceDisappeared(AssociationInfo association) {
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ Slog.i(TAG, "notifyDevice_Disappeared() id=" + association.getId() + " u" + userId
+ + "/" + packageName);
+
+ final CompanionDeviceServiceConnector primaryServiceConnector =
+ getPrimaryServiceConnector(userId, packageName);
+ if (primaryServiceConnector == null) {
+ Slog.e(TAG, "notify_CompanionApplicationDevice_Disappeared(): "
+ + "u" + userId + "/" + packageName + " is NOT bound.");
+ Slog.e(TAG, "Stacktrace", new Throwable());
+ return;
+ }
+
+ Log.i(TAG, "Calling onDeviceDisappeared to userId=[" + userId + "] package=["
+ + packageName + "] associationId=[" + association.getId() + "]");
+
+ primaryServiceConnector.postOnDeviceDisappeared(association);
+ }
+
+ /**
+ * Notify the app that the device appeared.
+ */
+ public void notifyCompanionDevicePresenceEvent(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, "notifyCompanionApplicationDevicePresenceEvent(): "
+ + "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 + "] associationId=[" + association.getId()
+ + "] event=[" + event + "]");
+
+ primaryServiceConnector.postOnDevicePresenceEvent(devicePresenceEvent);
+ }
+
+ /**
+ * Notify the app that the device disappeared.
+ */
+ public void notifyUuidDevicePresenceEvent(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) {
+ out.append("Companion Device Application Controller: \n");
+
+ synchronized (mBoundCompanionApplications) {
+ out.append(" Bound Companion Applications: ");
+ if (mBoundCompanionApplications.size() == 0) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ mBoundCompanionApplications.dump(out);
+ }
+ }
+
+ out.append(" Companion Applications Scheduled For Rebinding: ");
+ if (mScheduledForRebindingCompanionApplications.size() == 0) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ mScheduledForRebindingCompanionApplications.dump(out);
+ }
+ }
+
+ /**
+ * Rebinding for Self-Managed secondary services OR Non-Self-Managed services.
+ */
+ private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionDeviceServiceConnector serviceConnector) {
+
+ boolean isPrimary = serviceConnector.isPrimary();
+ Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
+
+ // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
+ if (isPrimary) {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
+
+ for (AssociationInfo association : associations) {
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ break;
+ }
+ }
+
+ synchronized (mBoundCompanionApplications) {
+ mBoundCompanionApplications.removePackage(userId, packageName);
+ }
+ }
+
+ // Second: schedule rebinding if needed.
+ final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
+
+ if (shouldScheduleRebind) {
+ scheduleRebinding(userId, packageName, serviceConnector);
+ }
+ }
+
+ private @Nullable CompanionDeviceServiceConnector getPrimaryServiceConnector(
+ @UserIdInt int userId, @NonNull String packageName) {
+ final List<CompanionDeviceServiceConnector> connectors;
+ synchronized (mBoundCompanionApplications) {
+ connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName);
+ }
+ return connectors != null ? connectors.get(0) : null;
+ }
+
+ private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
+ // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
+ // app is uninstalled.
+ boolean stillAssociated = false;
+ // 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.getActiveAssociationsByPackage(userId, packageName)) {
+ final int associationId = ai.getId();
+ stillAssociated = true;
+ if (ai.isSelfManaged()) {
+ // Do not rebind if primary one is died for selfManaged application.
+ if (isPrimary
+ && mDevicePresenceMonitor.isDevicePresent(associationId)) {
+ mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
+ shouldScheduleRebind = false;
+ }
+ // Do not rebind if both primary and secondary services are died for
+ // selfManaged application.
+ shouldScheduleRebind = isCompanionApplicationBound(userId, packageName);
+ } else if (ai.isNotifyOnDeviceNearby()) {
+ // Always rebind for non-selfManaged devices.
+ shouldScheduleRebind = true;
+ }
+ }
+
+ 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>>> {
+ @Override
+ public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+ @UserIdInt int userId) {
+ return super.forUser(userId);
+ }
+
+ synchronized @NonNull List<ComponentName> forPackage(
+ @UserIdInt int userId, @NonNull String packageName) {
+ return forUser(userId).getOrDefault(packageName, Collections.emptyList());
+ }
+
+ synchronized void invalidate(@UserIdInt int userId) {
+ remove(userId);
+ }
+
+ @Override
+ protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+ return PackageUtils.getCompanionServicesForUser(mContext, userId);
+ }
+ }
+
+ /**
+ * Associates an Android package (defined by userId + packageName) with a value of type T.
+ */
+ private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> {
+
+ void setValueForPackage(
+ @UserIdInt int userId, @NonNull String packageName, @NonNull T value) {
+ Map<String, T> forUser = get(userId);
+ if (forUser == null) {
+ forUser = /* Map<String, T> */ new HashMap();
+ put(userId, forUser);
+ }
+
+ forUser.put(packageName, value);
+ }
+
+ boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ final Map<String, ?> forUser = get(userId);
+ return forUser != null && forUser.containsKey(packageName);
+ }
+
+ T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ final Map<String, T> forUser = get(userId);
+ return forUser != null ? forUser.get(packageName) : null;
+ }
+
+ T removePackage(@UserIdInt int userId, @NonNull String packageName) {
+ final Map<String, T> forUser = get(userId);
+ if (forUser == null) return null;
+ return forUser.remove(packageName);
+ }
+
+ void dump() {
+ if (size() == 0) {
+ Log.d(TAG, "<empty>");
+ return;
+ }
+
+ for (int i = 0; i < size(); i++) {
+ final int userId = keyAt(i);
+ final Map<String, T> forUser = get(userId);
+ if (forUser.isEmpty()) {
+ Log.d(TAG, "u" + userId + ": <empty>");
+ }
+
+ for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
+ final String packageName = packageValue.getKey();
+ final T value = packageValue.getValue();
+ Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value);
+ }
+ }
+ }
+
+ private void dump(@NonNull PrintWriter out) {
+ for (int i = 0; i < size(); i++) {
+ final int userId = keyAt(i);
+ final Map<String, T> forUser = get(userId);
+ if (forUser.isEmpty()) {
+ out.append(" u").append(String.valueOf(userId)).append(": <empty>\n");
+ }
+
+ for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
+ final String packageName = packageValue.getKey();
+ final T value = packageValue.getValue();
+ out.append(" u").append(String.valueOf(userId)).append("\\")
+ .append(packageName).append(" -> ")
+ .append(value.toString()).append('\n');
+ }
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index f4f6c13e74e4..712162b2d3b5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,10 +20,15 @@ package com.android.server.companion;
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_COMPANION_SELF_MANAGED;
import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE;
import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+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;
@@ -37,10 +42,13 @@ import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObservingDevicePresenceByUuid;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import android.annotation.EnforcePermission;
@@ -56,6 +64,7 @@ import android.app.PendingIntent;
import android.bluetooth.BluetoothDevice;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
+import android.companion.DeviceNotAssociatedException;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
@@ -70,6 +79,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
+import android.hardware.power.Mode;
import android.net.MacAddress;
import android.net.NetworkPolicyManager;
import android.os.Binder;
@@ -81,6 +91,7 @@ import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
@@ -107,8 +118,7 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -121,7 +131,10 @@ import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
@SuppressLint("LongLogTag")
@@ -133,6 +146,10 @@ public class CompanionDeviceManagerService extends SystemService {
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
+ private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
+ "debug.cdm.cdmservice.removal_time_window";
+
+ private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
private static final int MAX_CN_LENGTH = 500;
private final ActivityTaskManagerInternal mAtmInternal;
@@ -148,11 +165,10 @@ public class CompanionDeviceManagerService extends SystemService {
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final BackupRestoreProcessor mBackupRestoreProcessor;
- private final DevicePresenceProcessor mDevicePresenceMonitor;
- private final CompanionAppBinder mCompanionAppController;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final CompanionApplicationController mCompanionAppController;
private final CompanionTransportManager mTransportManager;
private final DisassociationProcessor mDisassociationProcessor;
- private final InactiveAssociationsRemovalService mInactiveAssociationsRemovalService;
private final CrossDeviceSyncController mCrossDeviceSyncController;
public CompanionDeviceManagerService(Context context) {
@@ -169,7 +185,7 @@ public class CompanionDeviceManagerService extends SystemService {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
- mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
+ mAssociationStore = new AssociationStore(userManager, associationDiskStore);
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mObservableUuidStore = new ObservableUuidStore();
@@ -180,11 +196,11 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
mAssociationRequestsProcessor);
- mCompanionAppController = new CompanionAppBinder(
- context, mAssociationStore, mObservableUuidStore, mPowerManagerInternal);
+ mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager,
+ mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
- mDevicePresenceMonitor = new DevicePresenceProcessor(context,
- mCompanionAppController, userManager, mAssociationStore, mObservableUuidStore,
+ mCompanionAppController = new CompanionApplicationController(
+ context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
mPowerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
@@ -193,9 +209,6 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor,
mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager);
- mInactiveAssociationsRemovalService = new InactiveAssociationsRemovalService(
- mAssociationStore, mDisassociationProcessor);
-
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
@@ -289,6 +302,181 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
+ @NonNull
+ AssociationInfo getAssociationWithCallerChecks(
+ @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
+ userId, packageName, macAddress);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
+ }
+
+ @NonNull
+ AssociationInfo getAssociationWithCallerChecks(int associationId) {
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ association = sanitizeWithCallerChecks(getContext(), association);
+ if (association != null) {
+ return association;
+ } else {
+ throw new IllegalArgumentException("Association does not exist "
+ + "or the caller does not have permissions to manage it "
+ + "(ie. it belongs to a different package or a different user).");
+ }
+ }
+
+ private void onDeviceAppearedInternal(int associationId) {
+ if (DEBUG) Log.i(TAG, "onDevice_Appeared_Internal() id=" + associationId);
+
+ final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (DEBUG) Log.d(TAG, " association=" + association);
+
+ if (!association.shouldBindWhenPresent()) return;
+
+ bindApplicationIfNeeded(association);
+
+ mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association);
+ }
+
+ private void onDeviceDisappearedInternal(int associationId) {
+ if (DEBUG) Log.i(TAG, "onDevice_Disappeared_Internal() id=" + associationId);
+
+ final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (DEBUG) Log.d(TAG, " association=" + association);
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ if (DEBUG) Log.w(TAG, "u" + userId + "\\" + packageName + " is NOT bound");
+ return;
+ }
+
+ if (association.shouldBindWhenPresent()) {
+ mCompanionAppController.notifyCompanionApplicationDeviceDisappeared(association);
+ }
+ }
+
+ 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 EVENT_BLE_APPEARED:
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
+ if (!association.shouldBindWhenPresent()) return;
+
+ bindApplicationIfNeeded(association);
+
+ mCompanionAppController.notifyCompanionDevicePresenceEvent(
+ association, event);
+ break;
+ 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.notifyCompanionDevicePresenceEvent(
+ association, event);
+ }
+ // Check if there are other devices associated to the app that are present.
+ if (shouldBindPackage(userId, packageName)) return;
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + "is not supported");
+ break;
+ }
+ }
+
+ 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.notifyUuidDevicePresenceEvent(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.notifyUuidDevicePresenceEvent(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();
+ // Set bindImportant to true when the association is self-managed to avoid the target
+ // service being killed.
+ final boolean bindImportant = association.isSelfManaged();
+ if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
+ mCompanionAppController.bindCompanionApplication(
+ userId, packageName, bindImportant);
+ } else if (DEBUG) {
+ Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound");
+ }
+ }
+
+ /**
+ * @return whether the package should be bound (i.e. at least one of the devices associated with
+ * 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.getActiveAssociationsByPackage(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;
+ }
+
private void onPackageRemoveOrDataClearedInternal(
@UserIdInt int userId, @NonNull String packageName) {
if (DEBUG) {
@@ -334,8 +522,27 @@ public class CompanionDeviceManagerService extends SystemService {
mBackupRestoreProcessor.restorePendingAssociations(userId, packageName);
}
+ // Revoke associations if the selfManaged companion device does not connect for 3 months.
void removeInactiveSelfManagedAssociations() {
- mInactiveAssociationsRemovalService.removeIdleSelfManagedAssociations();
+ final long currentTime = System.currentTimeMillis();
+ long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
+ if (removalWindow <= 0) {
+ // 0 or negative values indicate that the sysprop was never set or should be ignored.
+ removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
+ }
+
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
+ if (!association.isSelfManaged()) continue;
+
+ final boolean isInactive =
+ currentTime - association.getLastTimeConnectedMs() >= removalWindow;
+ if (!isInactive) continue;
+
+ final int id = association.getId();
+
+ Slog.i(TAG, "Removing inactive self-managed association id=" + id);
+ mDisassociationProcessor.disassociate(id);
+ }
}
public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@@ -472,15 +679,24 @@ public class CompanionDeviceManagerService extends SystemService {
@Deprecated
@Override
public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
+ Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName
+ + ", macAddress=" + deviceMacAddress);
+
requireNonNull(deviceMacAddress);
requireNonNull(packageName);
- mDisassociationProcessor.disassociate(userId, packageName, deviceMacAddress);
+ final AssociationInfo association =
+ getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
+ mDisassociationProcessor.disassociate(association.getId());
}
@Override
public void disassociate(int associationId) {
- mDisassociationProcessor.disassociate(associationId);
+ Slog.i(TAG, "disassociate() associationId=" + associationId);
+
+ final AssociationInfo association =
+ getAssociationWithCallerChecks(associationId);
+ mDisassociationProcessor.disassociate(association.getId());
}
@Override
@@ -542,25 +758,21 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void legacyStartObservingDevicePresence(String deviceAddress, String callingPackage,
- int userId) throws RemoteException {
- legacyStartObservingDevicePresence_enforcePermission();
-
- mDevicePresenceMonitor.startObservingDevicePresence(userId, callingPackage,
- deviceAddress);
+ public void registerDevicePresenceListenerService(String deviceAddress,
+ String callingPackage, int userId) throws RemoteException {
+ registerDevicePresenceListenerService_enforcePermission();
+ // TODO: take the userId into account.
+ registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
}
@Override
- @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void legacyStopObservingDevicePresence(String deviceAddress, String callingPackage,
- int userId) throws RemoteException {
- legacyStopObservingDevicePresence_enforcePermission();
-
- mDevicePresenceMonitor.stopObservingDevicePresence(userId, callingPackage,
- deviceAddress);
+ public void unregisterDevicePresenceListenerService(String deviceAddress,
+ String callingPackage, int userId) throws RemoteException {
+ unregisterDevicePresenceListenerService_enforcePermission();
+ // TODO: take the userId into account.
+ registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
}
@Override
@@ -568,8 +780,7 @@ public class CompanionDeviceManagerService extends SystemService {
public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
startObservingDevicePresence_enforcePermission();
-
- mDevicePresenceMonitor.startObservingDevicePresence(request, packageName, userId);
+ registerDevicePresenceListener(request, packageName, userId, /* active */ true);
}
@Override
@@ -577,8 +788,80 @@ public class CompanionDeviceManagerService extends SystemService {
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();
- mDevicePresenceMonitor.stopObservingDevicePresence(request, packageName, userId);
+ 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
@@ -591,7 +874,8 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public boolean isPermissionTransferUserConsented(String packageName, int userId,
int associationId) {
- return mSystemDataTransferProcessor.isPermissionTransferUserConsented(associationId);
+ return mSystemDataTransferProcessor.isPermissionTransferUserConsented(packageName,
+ userId, associationId);
}
@Override
@@ -607,7 +891,8 @@ public class CompanionDeviceManagerService extends SystemService {
ParcelFileDescriptor fd) {
attachSystemDataTransport_enforcePermission();
- mTransportManager.attachSystemDataTransport(associationId, fd);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
}
@Override
@@ -615,56 +900,96 @@ public class CompanionDeviceManagerService extends SystemService {
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
detachSystemDataTransport_enforcePermission();
- mTransportManager.detachSystemDataTransport(associationId);
- }
-
- @Override
- @EnforcePermission(MANAGE_COMPANION_DEVICES)
- public void enableSecureTransport(boolean enabled) {
- enableSecureTransport_enforcePermission();
-
- mTransportManager.enableSecureTransport(enabled);
+ getAssociationWithCallerChecks(associationId);
+ mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
}
@Override
public void enableSystemDataSync(int associationId, int flags) {
+ getAssociationWithCallerChecks(associationId);
mAssociationRequestsProcessor.enableSystemDataSync(associationId, flags);
}
@Override
public void disableSystemDataSync(int associationId, int flags) {
+ getAssociationWithCallerChecks(associationId);
mAssociationRequestsProcessor.disableSystemDataSync(associationId, flags);
}
@Override
public void enablePermissionsSync(int associationId) {
+ getAssociationWithCallerChecks(associationId);
mSystemDataTransferProcessor.enablePermissionsSync(associationId);
}
@Override
public void disablePermissionsSync(int associationId) {
+ getAssociationWithCallerChecks(associationId);
mSystemDataTransferProcessor.disablePermissionsSync(associationId);
}
@Override
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+ // TODO: temporary fix, will remove soon
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ if (association == null) {
+ return null;
+ }
+ getAssociationWithCallerChecks(associationId);
return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
}
@Override
- @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
- public void notifySelfManagedDeviceAppeared(int associationId) {
- notifySelfManagedDeviceAppeared_enforcePermission();
+ @EnforcePermission(MANAGE_COMPANION_DEVICES)
+ public void enableSecureTransport(boolean enabled) {
+ enableSecureTransport_enforcePermission();
+ mTransportManager.enableSecureTransport(enabled);
+ }
- mDevicePresenceMonitor.notifySelfManagedDevicePresenceEvent(associationId, true);
+ @Override
+ public void notifyDeviceAppeared(int associationId) {
+ if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId);
+
+ AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association with ID " + associationId
+ + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+ + " self-managed associations.");
+ }
+ // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+ // timestamp.
+ association = (new AssociationInfo.Builder(association))
+ .setLastTimeConnected(System.currentTimeMillis())
+ .build();
+ mAssociationStore.updateAssociation(association);
+
+ mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId);
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, true);
+ }
}
@Override
- @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
- public void notifySelfManagedDeviceDisappeared(int associationId) {
- notifySelfManagedDeviceDisappeared_enforcePermission();
+ public void notifyDeviceDisappeared(int associationId) {
+ if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId);
+
+ final AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ if (!association.isSelfManaged()) {
+ throw new IllegalArgumentException("Association with ID " + associationId
+ + " is not self-managed. notifyDeviceAppeared(int) can only be called for"
+ + " self-managed associations.");
+ }
+
+ mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId);
- mDevicePresenceMonitor.notifySelfManagedDevicePresenceEvent(associationId, false);
+ final String deviceProfile = association.getDeviceProfile();
+ if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
+ Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
+ mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
+ }
}
@Override
@@ -672,6 +997,66 @@ public class CompanionDeviceManagerService extends SystemService {
return mCompanionAppController.isCompanionApplicationBound(userId, packageName);
}
+ private void registerDevicePresenceListenerActive(String packageName, String deviceAddress,
+ boolean active) throws RemoteException {
+ if (DEBUG) {
+ Log.i(TAG, "registerDevicePresenceListenerActive()"
+ + " active=" + active
+ + " deviceAddress=" + deviceAddress);
+ }
+ final int userId = getCallingUserId();
+ enforceCallerIsSystemOr(userId, packageName);
+
+ AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(
+ userId, packageName, deviceAddress);
+
+ if (association == null) {
+ throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
+ + " is not associated with device " + deviceAddress
+ + " 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.");
+ return;
+ }
+
+ // AssociationInfo class is immutable: create a new AssociationInfo object with updated
+ // flag.
+ association = (new AssociationInfo.Builder(association))
+ .setNotifyOnDeviceNearby(active)
+ .build();
+ // Do not need to call {@link BleCompanionDeviceScanner#restartScan()} since it will
+ // trigger {@link BleCompanionDeviceScanner#restartScan(int, AssociationInfo)} when
+ // an application sets/unsets the mNotifyOnDeviceNearby flag.
+ mAssociationStore.updateAssociation(association);
+
+ int associationId = association.getId();
+ // If device is already present, then trigger callback.
+ if (active && mDevicePresenceMonitor.isDevicePresent(associationId)) {
+ Slog.i(TAG, "Device is already present. Triggering callback.");
+ if (mDevicePresenceMonitor.isBlePresent(associationId)
+ || mDevicePresenceMonitor.isSimulatePresent(associationId)) {
+ onDeviceAppearedInternal(associationId);
+ onDevicePresenceEventInternal(associationId, EVENT_BLE_APPEARED);
+ } else if (mDevicePresenceMonitor.isBtConnected(associationId)) {
+ onDevicePresenceEventInternal(associationId, EVENT_BT_CONNECTED);
+ }
+ }
+
+ // If last listener is unregistered, then unbind application.
+ if (!active && !shouldBindPackage(userId, packageName)) {
+ if (DEBUG) Log.d(TAG, "Last listener unregistered. Unbinding application.");
+ mCompanionAppController.unbindCompanionApplication(userId, packageName);
+ }
+ }
+
@Override
@EnforcePermission(ASSOCIATE_COMPANION_DEVICES)
public void createAssociation(String packageName, String macAddress, int userId,
@@ -685,8 +1070,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
- mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null);
+ createNewAssociation(userId, packageName, macAddressObj, null, null, false);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -715,7 +1099,9 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void setAssociationTag(int associationId, String tag) {
- mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
+ AssociationInfo association = getAssociationWithCallerChecks(associationId);
+ association = (new AssociationInfo.Builder(association)).setTag(tag).build();
+ mAssociationStore.updateAssociation(association);
}
@Override
@@ -760,6 +1146,14 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
+ void createNewAssociation(@UserIdInt int userId, @NonNull String packageName,
+ @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+ @Nullable String deviceProfile, boolean isSelfManaged) {
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
+ displayName, deviceProfile, /* associatedDevice */ null, isSelfManaged,
+ /* callback */ null, /* resultReceiver */ null);
+ }
+
/**
* Update special access for the association's package
*/
@@ -775,6 +1169,8 @@ public class CompanionDeviceManagerService extends SystemService {
return;
}
+ Slog.i(TAG, "Updating special access for package=[" + packageInfo.packageName + "]...");
+
if (containsEither(packageInfo.requestedPermissions,
android.Manifest.permission.RUN_IN_BACKGROUND,
android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
@@ -884,6 +1280,29 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
+ private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback =
+ new CompanionDevicePresenceMonitor.Callback() {
+ @Override
+ public void onDeviceAppeared(int associationId) {
+ onDeviceAppearedInternal(associationId);
+ }
+
+ @Override
+ public void onDeviceDisappeared(int associationId) {
+ onDeviceDisappearedInternal(associationId);
+ }
+
+ @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() {
@Override
public void onPackageRemoved(String packageName, int uid) {
@@ -896,7 +1315,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- public void onPackageModified(@NonNull String packageName) {
+ public void onPackageModified(String packageName) {
onPackageModifiedInternal(getChangingUserId(), packageName);
}
@@ -906,12 +1325,28 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
+ private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
+ final Map<String, Set<Integer>> copy = new HashMap<>();
+
+ for (Map.Entry<String, Set<Integer>> entry : orig.entrySet()) {
+ final Set<Integer> valueCopy = new HashSet<>(entry.getValue());
+ copy.put(entry.getKey(), Collections.unmodifiableSet(valueCopy));
+ }
+
+ return Collections.unmodifiableMap(copy);
+ }
+
private static <T> boolean containsEither(T[] array, T a, T b) {
return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
}
private class LocalService implements CompanionDeviceManagerServiceInternal {
@Override
+ public void removeInactiveSelfManagedAssociations() {
+ CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations();
+ }
+
+ @Override
public void registerCallMetadataSyncCallback(CrossDeviceSyncControllerCallback callback,
@CrossDeviceSyncControllerCallback.Type int type) {
if (CompanionDeviceConfig.isEnabled(
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
index e3b4c95a7dab..cdf832f8c788 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -28,6 +28,11 @@ import java.util.Collection;
*/
public interface CompanionDeviceManagerServiceInternal {
/**
+ * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations
+ */
+ void removeInactiveSelfManagedAssociations();
+
+ /**
* Registers a callback from an InCallService / ConnectionService to CDM to process sync
* requests and perform call control actions.
*/
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
index c01c3195e04d..5abdb42b34fc 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion.presence;
+package com.android.server.companion;
import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
@@ -33,42 +33,36 @@ import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
-import android.util.Slog;
+import android.util.Log;
import com.android.internal.infra.ServiceConnector;
import com.android.server.ServiceThread;
-import com.android.server.companion.CompanionDeviceManagerService;
/**
* Manages a connection (binding) to an instance of {@link CompanionDeviceService} running in the
* application process.
*/
@SuppressLint("LongLogTag")
-public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
-
- /** Listener for changes to the state of the {@link CompanionServiceConnector} */
- public interface Listener {
- /**
- * Called when service binding is died.
- */
- void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionServiceConnector serviceConnector);
- }
-
+class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
private static final String TAG = "CDM_CompanionServiceConnector";
+ private static final boolean DEBUG = false;
/* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
private static final long UNBIND_POST_DELAY_MS = 5_000;
- @UserIdInt
- private final int mUserId;
- @NonNull
- private final ComponentName mComponentName;
- private final boolean mIsPrimary;
+
+ /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */
+ interface Listener {
+ void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull CompanionDeviceServiceConnector serviceConnector);
+ }
+
+ private final @UserIdInt int mUserId;
+ private final @NonNull ComponentName mComponentName;
// IMPORTANT: this can (and will!) be null (at the moment, CompanionApplicationController only
// installs a listener to the primary ServiceConnector), hence we should always null-check the
// reference before calling on it.
- @Nullable
- private Listener mListener;
+ private @Nullable Listener mListener;
+ private boolean mIsPrimary;
/**
* Create a CompanionDeviceServiceConnector instance.
@@ -85,16 +79,16 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
* IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the
* service importance level should be higher than 125.
*/
- static CompanionServiceConnector newInstance(@NonNull Context context,
+ static CompanionDeviceServiceConnector newInstance(@NonNull Context context,
@UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged,
boolean isPrimary) {
final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
: BIND_ALMOST_PERCEPTIBLE;
- return new CompanionServiceConnector(
+ return new CompanionDeviceServiceConnector(
context, userId, componentName, bindingFlags, isPrimary);
}
- private CompanionServiceConnector(@NonNull Context context, @UserIdInt int userId,
+ private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
@NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) {
super(context, buildIntent(componentName), bindingFlags, userId, null);
mUserId = userId;
@@ -139,7 +133,6 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
return mIsPrimary;
}
- @NonNull
ComponentName getComponentName() {
return mComponentName;
}
@@ -147,15 +140,17 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
@Override
protected void onServiceConnectionStatusChanged(
@NonNull ICompanionDeviceService service, boolean isConnected) {
- Slog.d(TAG, "onServiceConnectionStatusChanged() " + mComponentName.toShortString()
- + " connected=" + isConnected);
+ if (DEBUG) {
+ Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
+ + " connected=" + isConnected);
+ }
}
@Override
public void binderDied() {
super.binderDied();
- Slog.d(TAG, "binderDied() " + mComponentName.toShortString());
+ if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString());
// Handle primary process being killed
if (mListener != null) {
@@ -177,8 +172,7 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
* within system_server and thus tends to get heavily congested)
*/
@Override
- @NonNull
- protected Handler getJobHandler() {
+ protected @NonNull Handler getJobHandler() {
return getServiceThread().getThreadHandler();
}
@@ -188,14 +182,12 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
return -1;
}
- @NonNull
- private static Intent buildIntent(@NonNull ComponentName componentName) {
+ private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
.setComponent(componentName);
}
- @NonNull
- private static ServiceThread getServiceThread() {
+ private static @NonNull ServiceThread getServiceThread() {
if (sServiceThread == null) {
synchronized (CompanionDeviceManagerService.class) {
if (sServiceThread == null) {
@@ -214,6 +206,5 @@ public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionD
* <p>
* Do NOT reference directly, use {@link #getServiceThread()} method instead.
*/
- @Nullable
- private static volatile ServiceThread sServiceThread;
+ private static volatile @Nullable ServiceThread sServiceThread;
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index a78938400a1e..a7a73cb6bddb 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,6 +18,8 @@ package com.android.server.companion;
import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
+import static com.android.server.companion.utils.PermissionsUtils.sanitizeWithCallerChecks;
+
import android.companion.AssociationInfo;
import android.companion.ContextSyncMessage;
import android.companion.Flags;
@@ -36,7 +38,7 @@ import com.android.server.companion.association.DisassociationProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -49,7 +51,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
private final CompanionDeviceManagerService mService;
private final DisassociationProcessor mDisassociationProcessor;
private final AssociationStore mAssociationStore;
- private final DevicePresenceProcessor mDevicePresenceProcessor;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -58,7 +60,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore,
- DevicePresenceProcessor devicePresenceProcessor,
+ CompanionDevicePresenceMonitor devicePresenceMonitor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor,
@@ -66,7 +68,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
DisassociationProcessor disassociationProcessor) {
mService = service;
mAssociationStore = associationStore;
- mDevicePresenceProcessor = devicePresenceProcessor;
+ mDevicePresenceMonitor = devicePresenceMonitor;
mTransportManager = transportManager;
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
@@ -83,7 +85,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
associationId = getNextIntArgRequired();
int event = getNextIntArgRequired();
- mDevicePresenceProcessor.simulateDeviceEvent(associationId, event);
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
return 0;
}
@@ -95,7 +97,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
ObservableUuid observableUuid = new ObservableUuid(
userId, ParcelUuid.fromString(uuid), packageName,
System.currentTimeMillis());
- mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event);
+ mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
return 0;
}
@@ -122,9 +124,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
String address = getNextArgRequired();
String deviceProfile = getNextArg();
final MacAddress macAddress = MacAddress.fromString(address);
- mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, false,
- /* callback */ null, /* resultReceiver */ null);
+ mService.createNewAssociation(userId, packageName, macAddress,
+ /* displayName= */ deviceProfile, deviceProfile, false);
}
break;
@@ -133,13 +134,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
- mAssociationStore.getFirstAssociationByAddress(userId, packageName,
- address);
- if (association == null) {
- out.println("Association doesn't exist.");
- } else {
- mDisassociationProcessor.disassociate(association.getId());
- }
+ mService.getAssociationWithCallerChecks(userId, packageName, address);
+ mDisassociationProcessor.disassociate(association.getId());
}
break;
@@ -148,7 +144,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
final List<AssociationInfo> userAssociations =
mAssociationStore.getAssociationsByUser(userId);
for (AssociationInfo association : userAssociations) {
- mDisassociationProcessor.disassociate(association.getId());
+ if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
+ mDisassociationProcessor.disassociate(association.getId());
+ }
}
}
break;
@@ -159,12 +157,12 @@ class CompanionDeviceShellCommand extends ShellCommand {
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
- mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 0);
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0);
break;
case "simulate-device-disappeared":
associationId = getNextIntArgRequired();
- mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 1);
+ mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
break;
case "get-backup-payload": {
@@ -412,9 +410,10 @@ class CompanionDeviceShellCommand extends ShellCommand {
pw.println(" Remove an existing Association.");
pw.println(" disassociate-all USER_ID");
pw.println(" Remove all Associations for a user.");
- pw.println(" refresh-cache");
+ pw.println(" clear-association-memory-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
- pw.println(" information from disk. USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
+ pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
pw.println(" simulate-device-appeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has appeared.");
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index a18776e67200..a02d9f912bcd 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -145,8 +145,7 @@ public class AssociationRequestsProcessor {
/**
* Handle incoming {@link AssociationRequest}s, sent via
- * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest,
- * IAssociationRequestCallback, String, int)}
+ * {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
*/
public void processNewAssociationRequest(@NonNull AssociationRequest request,
@NonNull String packageName, @UserIdInt int userId,
@@ -213,8 +212,7 @@ public class AssociationRequestsProcessor {
// 2b.4. Send the PendingIntent back to the app.
try {
callback.onAssociationPending(pendingIntent);
- } catch (RemoteException ignore) {
- }
+ } catch (RemoteException ignore) { }
}
/**
@@ -254,8 +252,7 @@ public class AssociationRequestsProcessor {
// forward it back to the application via the callback.
try {
callback.onFailure(e.getMessage());
- } catch (RemoteException ignore) {
- }
+ } catch (RemoteException ignore) { }
return;
}
@@ -325,8 +322,7 @@ public class AssociationRequestsProcessor {
* Enable system data sync.
*/
public void enableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() | flags).build();
mAssociationStore.updateAssociation(updated);
@@ -336,23 +332,12 @@ public class AssociationRequestsProcessor {
* Disable system data sync.
*/
public void disableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() & (~flags)).build();
mAssociationStore.updateAssociation(updated);
}
- /**
- * Set association tag.
- */
- public void setAssociationTag(int associationId, String tag) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
- mAssociationStore.updateAssociation(association);
- }
-
private void sendCallbackAndFinish(@Nullable AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -411,14 +396,14 @@ public class AssociationRequestsProcessor {
// If the application already has a pending association request, that PendingIntent
// will be cancelled except application wants to cancel the request by the system.
return Binder.withCleanCallingIdentity(() ->
- PendingIntent.getActivityAsUser(
- mContext, /*requestCode */ packageUid, intent,
- FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
- ActivityOptions.makeBasic()
- .setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- .toBundle(),
- UserHandle.CURRENT)
+ PendingIntent.getActivityAsUser(
+ mContext, /*requestCode */ packageUid, intent,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(),
+ UserHandle.CURRENT)
);
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index ae2b70852a35..edebb55233d0 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -18,7 +18,6 @@ package com.android.server.companion.association;
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
-import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,7 +26,6 @@ import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.companion.IOnAssociationsChangedListener;
-import android.content.Context;
import android.content.pm.UserInfo;
import android.net.MacAddress;
import android.os.Binder;
@@ -59,22 +57,21 @@ import java.util.concurrent.Executors;
@SuppressLint("LongLogTag")
public class AssociationStore {
- @IntDef(prefix = {"CHANGE_TYPE_"}, value = {
+ @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
CHANGE_TYPE_ADDED,
CHANGE_TYPE_REMOVED,
CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
})
@Retention(RetentionPolicy.SOURCE)
- public @interface ChangeType {
- }
+ public @interface ChangeType {}
public static final int CHANGE_TYPE_ADDED = 0;
public static final int CHANGE_TYPE_REMOVED = 1;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
- /** Listener for any changes to associations. */
+ /** Listener for any changes to associations. */
public interface OnChangeListener {
/**
* Called when there are association changes.
@@ -103,30 +100,25 @@ public class AssociationStore {
/**
* Called when an association is added.
*/
- default void onAssociationAdded(AssociationInfo association) {
- }
+ default void onAssociationAdded(AssociationInfo association) {}
/**
* Called when an association is removed.
*/
- default void onAssociationRemoved(AssociationInfo association) {
- }
+ default void onAssociationRemoved(AssociationInfo association) {}
/**
* Called when an association is updated.
*/
- default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
- }
+ default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
}
private static final String TAG = "CDM_AssociationStore";
- private final Context mContext;
- private final UserManager mUserManager;
- private final AssociationDiskStore mDiskStore;
+ private final Object mLock = new Object();
+
private final ExecutorService mExecutor;
- private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mPersisted = false;
@GuardedBy("mLock")
@@ -140,9 +132,10 @@ public class AssociationStore {
private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners =
new RemoteCallbackList<>();
- public AssociationStore(Context context, UserManager userManager,
- AssociationDiskStore diskStore) {
- mContext = context;
+ private final UserManager mUserManager;
+ private final AssociationDiskStore mDiskStore;
+
+ public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) {
mUserManager = userManager;
mDiskStore = diskStore;
mExecutor = Executors.newSingleThreadExecutor();
@@ -209,7 +202,7 @@ public class AssociationStore {
synchronized (mLock) {
if (mIdToAssociationMap.containsKey(id)) {
- Slog.e(TAG, "Association id=[" + id + "] already exists.");
+ Slog.e(TAG, "Association with id=[" + id + "] already exists.");
return;
}
@@ -456,26 +449,6 @@ public class AssociationStore {
}
/**
- * Get association by id with caller checks.
- */
- @NonNull
- public AssociationInfo getAssociationWithCallerChecks(int associationId) {
- AssociationInfo association = getAssociationById(associationId);
- if (association == null) {
- throw new IllegalArgumentException(
- "getAssociationWithCallerChecks() Association id=[" + associationId
- + "] doesn't exist.");
- }
- if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
- association.getPackageName())) {
- return association;
- }
-
- throw new IllegalArgumentException(
- "The caller can't interact with the association id=[" + associationId + "].");
- }
-
- /**
* Register a local listener for association changes.
*/
public void registerLocalListener(@NonNull OnChangeListener listener) {
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index 20de1210dd9d..ec8977918c56 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -33,19 +33,18 @@ import android.os.Binder;
import android.os.UserHandle;
import android.util.Slog;
+import com.android.server.companion.CompanionApplicationController;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.transport.CompanionTransportManager;
/**
- * This class responsible for disassociation.
+ * A class response for Association removal.
*/
@SuppressLint("LongLogTag")
public class DisassociationProcessor {
private static final String TAG = "CDM_DisassociationProcessor";
-
@NonNull
private final Context mContext;
@NonNull
@@ -53,11 +52,11 @@ public class DisassociationProcessor {
@NonNull
private final PackageManagerInternal mPackageManagerInternal;
@NonNull
- private final DevicePresenceProcessor mDevicePresenceMonitor;
+ private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
- private final CompanionAppBinder mCompanionAppController;
+ private final CompanionApplicationController mCompanionAppController;
@NonNull
private final CompanionTransportManager mTransportManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
@@ -67,8 +66,8 @@ public class DisassociationProcessor {
@NonNull ActivityManager activityManager,
@NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
- @NonNull DevicePresenceProcessor devicePresenceMonitor,
- @NonNull CompanionAppBinder applicationController,
+ @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
+ @NonNull CompanionApplicationController applicationController,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull CompanionTransportManager companionTransportManager) {
mContext = context;
@@ -90,7 +89,11 @@ public class DisassociationProcessor {
public void disassociate(int id) {
Slog.i(TAG, "Disassociating id=[" + id + "]...");
- final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
+ final AssociationInfo association = mAssociationStore.getAssociationById(id);
+ if (association == null) {
+ Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist.");
+ return;
+ }
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -115,12 +118,12 @@ public class DisassociationProcessor {
return;
}
- // Detach transport if exists
- mTransportManager.detachSystemDataTransport(id);
-
// Association cleanup.
- mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
mAssociationStore.removeAssociation(association.getId());
+ mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
+
+ // Detach transport if exists
+ mTransportManager.detachSystemDataTransport(packageName, userId, id);
// If role is not in use by other associations, revoke the role.
// Do not need to remove the system role since it was pre-granted by the system.
@@ -144,24 +147,6 @@ public class DisassociationProcessor {
}
}
- /**
- * @deprecated Use {@link #disassociate(int)} instead.
- */
- @Deprecated
- public void disassociate(int userId, String packageName, String macAddress) {
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
- packageName, macAddress);
-
- if (association == null) {
- throw new IllegalArgumentException(
- "Association for mac address=[" + macAddress + "] doesn't exist");
- }
-
- mAssociationStore.getAssociationWithCallerChecks(association.getId());
-
- disassociate(association.getId());
- }
-
@SuppressLint("MissingPermission")
private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
return Binder.withCleanCallingIdentity(() -> {
@@ -178,7 +163,7 @@ public class DisassociationProcessor {
() -> mActivityManager.addOnUidImportanceListener(
mOnPackageVisibilityChangeListener,
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE));
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException e) {
Slog.e(TAG, "Failed to start listening to uid importance changes.");
}
}
diff --git a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index b52904aa5301..f28731548dcc 100644
--- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -22,14 +22,15 @@ import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
-import android.companion.AssociationInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.os.SystemProperties;
import android.util.Slog;
+import com.android.server.LocalServices;
+import com.android.server.companion.CompanionDeviceManagerServiceInternal;
+
/**
- * A Job Service responsible for clean up self-managed associations if it's idle for 90 days.
+ * A Job Service responsible for clean up idle self-managed associations.
*
* The job will be executed only if the device is charging and in idle mode due to the application
* will be killed if association/role are revoked. See {@link DisassociationProcessor}
@@ -40,25 +41,14 @@ public class InactiveAssociationsRemovalService extends JobService {
private static final String JOB_NAMESPACE = "companion";
private static final int JOB_ID = 1;
private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
- private static final String SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW =
- "debug.cdm.cdmservice.removal_time_window";
- private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
-
- private final AssociationStore mAssociationStore;
- private final DisassociationProcessor mDisassociationProcessor;
-
- public InactiveAssociationsRemovalService(AssociationStore associationStore,
- DisassociationProcessor disassociationProcessor) {
- mAssociationStore = associationStore;
- mDisassociationProcessor = disassociationProcessor;
- }
@Override
public boolean onStartJob(final JobParameters params) {
Slog.i(TAG, "Execute the Association Removal job");
-
- removeIdleSelfManagedAssociations();
-
+ // Special policy for selfManaged that need to revoke associations if the device
+ // does not connect for 90 days.
+ LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
+ .removeInactiveSelfManagedAssociations();
jobFinished(params, false);
return true;
}
@@ -87,29 +77,4 @@ public class InactiveAssociationsRemovalService extends JobService {
.build();
jobScheduler.schedule(job);
}
-
- /**
- * Remove idle self-managed associations.
- */
- public void removeIdleSelfManagedAssociations() {
- final long currentTime = System.currentTimeMillis();
- long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1);
- if (removalWindow <= 0) {
- // 0 or negative values indicate that the sysprop was never set or should be ignored.
- removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
- }
-
- for (AssociationInfo association : mAssociationStore.getAssociations()) {
- if (!association.isSelfManaged()) continue;
-
- final boolean isInactive =
- currentTime - association.getLastTimeConnectedMs() >= removalWindow;
- if (!isInactive) continue;
-
- final int id = association.getId();
-
- Slog.i(TAG, "Removing inactive self-managed association id=" + id);
- mDisassociationProcessor.disassociate(id);
- }
- }
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 9069689ee5eb..c5ca0bf7e9c5 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -31,6 +31,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
+import android.companion.DeviceNotAssociatedException;
import android.companion.IOnMessageReceivedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
@@ -55,6 +56,7 @@ import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.companion.utils.PackageUtils;
+import com.android.server.companion.utils.PermissionsUtils;
import java.util.List;
import java.util.concurrent.ExecutorService;
@@ -118,10 +120,28 @@ public class SystemDataTransferProcessor {
}
/**
+ * Resolve the requested association, throwing if the caller doesn't have
+ * adequate permissions.
+ */
+ @NonNull
+ private AssociationInfo resolveAssociation(String packageName, int userId,
+ int associationId) {
+ AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
+ if (association == null) {
+ throw new DeviceNotAssociatedException("Association "
+ + associationId + " is not associated with the app " + packageName
+ + " for user " + userId);
+ }
+ return association;
+ }
+
+ /**
* Return whether the user has consented to the permission transfer for the association.
*/
- public boolean isPermissionTransferUserConsented(int associationId) {
- mAssociationStore.getAssociationWithCallerChecks(associationId);
+ public boolean isPermissionTransferUserConsented(String packageName, @UserIdInt int userId,
+ int associationId) {
+ resolveAssociation(packageName, userId, associationId);
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
if (request == null) {
@@ -147,8 +167,7 @@ public class SystemDataTransferProcessor {
return null;
}
- final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
+ final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ "] associationId [" + associationId + "]");
@@ -188,7 +207,7 @@ public class SystemDataTransferProcessor {
Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName
+ "] userId [" + userId + "] associationId [" + associationId + "]");
- mAssociationStore.getAssociationWithCallerChecks(associationId);
+ final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
// Check if the request has been consented by the user.
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
@@ -220,20 +239,24 @@ public class SystemDataTransferProcessor {
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(true);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
+ Binder.withCleanCallingIdentity(() -> {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(true);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
+ });
}
/**
* Disable perm sync for the association
*/
public void disablePermissionsSync(int associationId) {
- int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(false);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
+ Binder.withCleanCallingIdentity(() -> {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(false);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
+ });
}
/**
@@ -241,17 +264,18 @@ public class SystemDataTransferProcessor {
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- int userId = mAssociationStore.getAssociationWithCallerChecks(associationId)
- .getUserId();
- List<SystemDataTransferRequest> requests =
- mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
- associationId);
- for (SystemDataTransferRequest request : requests) {
- if (request instanceof PermissionSyncRequest) {
- return (PermissionSyncRequest) request;
+ return Binder.withCleanCallingIdentity(() -> {
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+ List<SystemDataTransferRequest> requests =
+ mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
+ associationId);
+ for (SystemDataTransferRequest request : requests) {
+ if (request instanceof PermissionSyncRequest) {
+ return (PermissionSyncRequest) request;
+ }
}
- }
- return null;
+ return null;
+ });
}
/**
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 9c37881499bd..c89ce11c169d 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -33,7 +33,7 @@ import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import static java.util.Objects.requireNonNull;
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 2d345c48a8eb..cb363a7c9d7f 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -19,7 +19,7 @@ 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.DevicePresenceProcessor.DEBUG;
+import static com.android.server.companion.presence.CompanionDevicePresenceMonitor.DEBUG;
import static com.android.server.companion.utils.Utils.btDeviceToString;
import android.annotation.NonNull;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
deleted file mode 100644
index 4ba4e2ce6899..000000000000
--- a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
+++ /dev/null
@@ -1,392 +0,0 @@
-/*
- * Copyright (C) 2022 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.presence;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-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.PowerManagerInternal;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.PerUser;
-import com.android.server.companion.CompanionDeviceManagerService;
-import com.android.server.companion.association.AssociationStore;
-import com.android.server.companion.utils.PackageUtils;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Manages communication with companion applications via
- * {@link android.companion.ICompanionDeviceService} interface, including "connecting" (binding) to
- * the services, maintaining the connection (the binding), and invoking callback methods such as
- * {@link CompanionDeviceService#onDeviceAppeared(AssociationInfo)},
- * {@link CompanionDeviceService#onDeviceDisappeared(AssociationInfo)} and
- * {@link CompanionDeviceService#onDevicePresenceEvent(DevicePresenceEvent)} in the
- * application process.
- *
- * <p>
- * The following is the list of the APIs provided by {@link CompanionAppBinder} (to be
- * utilized by {@link CompanionDeviceManagerService}):
- * <ul>
- * <li> {@link #bindCompanionApplication(int, String, boolean, CompanionServiceConnector.Listener)}
- * <li> {@link #unbindCompanionApplication(int, String)}
- * <li> {@link #isCompanionApplicationBound(int, String)}
- * <li> {@link #isRebindingCompanionApplicationScheduled(int, String)}
- * </ul>
- *
- * @see CompanionDeviceService
- * @see android.companion.ICompanionDeviceService
- * @see CompanionServiceConnector
- */
-@SuppressLint("LongLogTag")
-public class CompanionAppBinder {
- private static final String TAG = "CDM_CompanionAppBinder";
-
- private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final AssociationStore mAssociationStore;
- @NonNull
- private final ObservableUuidStore mObservableUuidStore;
- @NonNull
- private final CompanionServicesRegister mCompanionServicesRegister;
-
- private final PowerManagerInternal mPowerManagerInternal;
-
- @NonNull
- @GuardedBy("mBoundCompanionApplications")
- private final AndroidPackageMap<List<CompanionServiceConnector>>
- mBoundCompanionApplications;
- @NonNull
- @GuardedBy("mScheduledForRebindingCompanionApplications")
- private final AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;
-
- public CompanionAppBinder(@NonNull Context context,
- @NonNull AssociationStore associationStore,
- @NonNull ObservableUuidStore observableUuidStore,
- @NonNull PowerManagerInternal powerManagerInternal) {
- mContext = context;
- mAssociationStore = associationStore;
- mObservableUuidStore = observableUuidStore;
- mPowerManagerInternal = powerManagerInternal;
- mCompanionServicesRegister = new CompanionServicesRegister();
- mBoundCompanionApplications = new AndroidPackageMap<>();
- mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
- }
-
- /**
- * On package changed.
- */
- public void onPackagesChanged(@UserIdInt int userId) {
- mCompanionServicesRegister.invalidate(userId);
- }
-
- /**
- * CDM binds to the companion app.
- */
- public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
- boolean isSelfManaged, CompanionServiceConnector.Listener listener) {
- Slog.i(TAG, "Binding user=[" + userId + "], package=[" + packageName + "], isSelfManaged=["
- + isSelfManaged + "]...");
-
- final List<ComponentName> companionServices =
- mCompanionServicesRegister.forPackage(userId, packageName);
- if (companionServices.isEmpty()) {
- Slog.e(TAG, "Can not bind companion applications u" + userId + "/" + packageName + ": "
- + "eligible CompanionDeviceService not found.\n"
- + "A CompanionDeviceService should declare an intent-filter for "
- + "\"android.companion.CompanionDeviceService\" action and require "
- + "\"android.permission.BIND_COMPANION_DEVICE_SERVICE\" permission.");
- return;
- }
-
- final List<CompanionServiceConnector> serviceConnectors = new ArrayList<>();
- synchronized (mBoundCompanionApplications) {
- if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
- Slog.w(TAG, "The package is ALREADY bound.");
- return;
- }
-
- for (int i = 0; i < companionServices.size(); i++) {
- boolean isPrimary = i == 0;
- serviceConnectors.add(CompanionServiceConnector.newInstance(mContext, userId,
- companionServices.get(i), isSelfManaged, isPrimary));
- }
-
- mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
- }
-
- // Set listeners for both Primary and Secondary connectors.
- for (CompanionServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.setListener(listener);
- }
-
- // Now "bind" all the connectors: the primary one and the rest of them.
- for (CompanionServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.connect();
- }
- }
-
- /**
- * CDM unbinds the companion app.
- */
- public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
- Slog.i(TAG, "Unbinding user=[" + userId + "], package=[" + packageName + "]...");
-
- final List<CompanionServiceConnector> serviceConnectors;
-
- synchronized (mBoundCompanionApplications) {
- serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
- }
-
- if (serviceConnectors == null) {
- Slog.e(TAG, "The package is not bound.");
- return;
- }
-
- for (CompanionServiceConnector serviceConnector : serviceConnectors) {
- serviceConnector.postUnbind();
- }
- }
-
- /**
- * @return whether the companion application is bound now.
- */
- public boolean isCompanionApplicationBound(@UserIdInt int userId, @NonNull String packageName) {
- synchronized (mBoundCompanionApplications) {
- return mBoundCompanionApplications.containsValueForPackage(userId, packageName);
- }
- }
-
- /**
- * Remove bound apps for package.
- */
- public void removePackage(int userId, String packageName) {
- synchronized (mBoundCompanionApplications) {
- mBoundCompanionApplications.removePackage(userId, packageName);
- }
- }
-
- /**
- * Schedule rebinding for the package.
- */
- public void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
- CompanionServiceConnector serviceConnector) {
- Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);
-
- if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
- Slog.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
- + serviceConnector.getComponentName());
- return;
- }
-
- if (serviceConnector.isPrimary()) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.setValueForPackage(
- userId, packageName, true);
- }
- }
-
- // Rebinding in 10 seconds.
- Handler.getMain().postDelayed(() ->
- onRebindingCompanionApplicationTimeout(userId, packageName,
- serviceConnector),
- REBIND_TIMEOUT);
- }
-
- private boolean isRebindingCompanionApplicationScheduled(
- @UserIdInt int userId, @NonNull String packageName) {
- synchronized (mScheduledForRebindingCompanionApplications) {
- return mScheduledForRebindingCompanionApplications.containsValueForPackage(
- userId, packageName);
- }
- }
-
- private void onRebindingCompanionApplicationTimeout(
- @UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionServiceConnector serviceConnector) {
- // Re-mark the application is bound.
- if (serviceConnector.isPrimary()) {
- synchronized (mBoundCompanionApplications) {
- if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
- List<CompanionServiceConnector> serviceConnectors =
- Collections.singletonList(serviceConnector);
- mBoundCompanionApplications.setValueForPackage(userId, packageName,
- serviceConnectors);
- }
- }
-
- synchronized (mScheduledForRebindingCompanionApplications) {
- mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
- }
- }
-
- serviceConnector.connect();
- }
-
- /**
- * Dump bound apps.
- */
- public void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Application Controller: \n");
-
- synchronized (mBoundCompanionApplications) {
- out.append(" Bound Companion Applications: ");
- if (mBoundCompanionApplications.size() == 0) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- mBoundCompanionApplications.dump(out);
- }
- }
-
- out.append(" Companion Applications Scheduled For Rebinding: ");
- synchronized (mScheduledForRebindingCompanionApplications) {
- if (mScheduledForRebindingCompanionApplications.size() == 0) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- mScheduledForRebindingCompanionApplications.dump(out);
- }
- }
- }
-
- @Nullable
- CompanionServiceConnector getPrimaryServiceConnector(
- @UserIdInt int userId, @NonNull String packageName) {
- final List<CompanionServiceConnector> connectors;
- synchronized (mBoundCompanionApplications) {
- connectors = mBoundCompanionApplications.getValueForPackage(userId, packageName);
- }
- return connectors != null ? connectors.get(0) : null;
- }
-
- private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
- @Override
- public synchronized @NonNull Map<String, List<ComponentName>> forUser(
- @UserIdInt int userId) {
- return super.forUser(userId);
- }
-
- synchronized @NonNull List<ComponentName> forPackage(
- @UserIdInt int userId, @NonNull String packageName) {
- return forUser(userId).getOrDefault(packageName, Collections.emptyList());
- }
-
- synchronized void invalidate(@UserIdInt int userId) {
- remove(userId);
- }
-
- @Override
- protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
- return PackageUtils.getCompanionServicesForUser(mContext, userId);
- }
- }
-
- /**
- * Associates an Android package (defined by userId + packageName) with a value of type T.
- */
- private static class AndroidPackageMap<T> extends SparseArray<Map<String, T>> {
-
- void setValueForPackage(
- @UserIdInt int userId, @NonNull String packageName, @NonNull T value) {
- Map<String, T> forUser = get(userId);
- if (forUser == null) {
- forUser = /* Map<String, T> */ new HashMap();
- put(userId, forUser);
- }
-
- forUser.put(packageName, value);
- }
-
- boolean containsValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, ?> forUser = get(userId);
- return forUser != null && forUser.containsKey(packageName);
- }
-
- T getValueForPackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, T> forUser = get(userId);
- return forUser != null ? forUser.get(packageName) : null;
- }
-
- T removePackage(@UserIdInt int userId, @NonNull String packageName) {
- final Map<String, T> forUser = get(userId);
- if (forUser == null) return null;
- return forUser.remove(packageName);
- }
-
- void dump() {
- if (size() == 0) {
- Log.d(TAG, "<empty>");
- return;
- }
-
- for (int i = 0; i < size(); i++) {
- final int userId = keyAt(i);
- final Map<String, T> forUser = get(userId);
- if (forUser.isEmpty()) {
- Log.d(TAG, "u" + userId + ": <empty>");
- }
-
- for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
- final String packageName = packageValue.getKey();
- final T value = packageValue.getValue();
- Log.d(TAG, "u" + userId + "\\" + packageName + " -> " + value);
- }
- }
- }
-
- private void dump(@NonNull PrintWriter out) {
- for (int i = 0; i < size(); i++) {
- final int userId = keyAt(i);
- final Map<String, T> forUser = get(userId);
- if (forUser.isEmpty()) {
- out.append(" u").append(String.valueOf(userId)).append(": <empty>\n");
- }
-
- for (Map.Entry<String, T> packageValue : forUser.entrySet()) {
- final String packageName = packageValue.getKey();
- final T value = packageValue.getValue();
- out.append(" u").append(String.valueOf(userId)).append("\\")
- .append(packageName).append(" -> ")
- .append(value.toString()).append('\n');
- }
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
new file mode 100644
index 000000000000..7a1a83f53315
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -0,0 +1,620 @@
+/*
+ * Copyright (C) 2022 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.presence;
+
+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;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.os.Binder;
+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 android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.association.AssociationStore;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Class responsible for monitoring companion devices' "presence" status (i.e.
+ * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
+ *
+ * <p>
+ * Should only be used by
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * to which it provides the following API:
+ * <ul>
+ * <li> {@link #onSelfManagedDeviceConnected(int)}
+ * <li> {@link #onSelfManagedDeviceDisconnected(int)}
+ * <li> {@link #isDevicePresent(int)}
+ * <li> {@link Callback#onDeviceAppeared(int) Callback.onDeviceAppeared(int)}
+ * <li> {@link Callback#onDeviceDisappeared(int) Callback.onDeviceDisappeared(int)}
+ * <li> {@link Callback#onDevicePresenceEvent(int, int)}}
+ * </ul>
+ */
+@SuppressLint("LongLogTag")
+public class CompanionDevicePresenceMonitor implements AssociationStore.OnChangeListener,
+ BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
+ static final boolean DEBUG = false;
+ private static final String TAG = "CDM_CompanionDevicePresenceMonitor";
+
+ /** Callback for notifying about changes to status of companion devices. */
+ public interface Callback {
+ /** Invoked when companion device is found nearby or connects. */
+ void onDeviceAppeared(int associationId);
+
+ /** Invoked when a companion device no longer seen nearby or disconnects. */
+ void onDeviceDisappeared(int associationId);
+
+ /** 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;
+
+ // NOTE: Same association may appear in more than one of the following sets at the same time.
+ // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
+ // companion applications, while at the same be connected via BT, or detected nearby by BLE
+ // scanner)
+ 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<>();
+ @GuardedBy("mBtDisconnectedDevices")
+ private final @NonNull Set<Integer> mBtDisconnectedDevices = new HashSet<>();
+
+ // A map to track device presence within 10 seconds of Bluetooth disconnection.
+ // The key is the association ID, and the boolean value indicates if the device
+ // was detected again within that time frame.
+ @GuardedBy("mBtDisconnectedDevices")
+ private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
+ new SparseBooleanArray();
+
+ // Tracking "simulated" presence. Used for debugging and testing only.
+ private final @NonNull Set<Integer> mSimulated = new HashSet<>();
+ private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
+ new SimulatedDevicePresenceSchedulerHelper();
+
+ private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
+ new BleDeviceDisappearedScheduler();
+
+ public CompanionDevicePresenceMonitor(UserManager userManager,
+ @NonNull AssociationStore associationStore,
+ @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
+ mAssociationStore = associationStore;
+ mObservableUuidStore = observableUuidStore;
+ mCallback = callback;
+ mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
+ associationStore, mObservableUuidStore,
+ /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
+ mBleScanner = new BleCompanionDeviceScanner(associationStore,
+ /* BleCompanionDeviceScanner.Callback */ this);
+ }
+
+ /** Initialize {@link CompanionDevicePresenceMonitor} */
+ public void init(Context context) {
+ if (DEBUG) Log.i(TAG, "init()");
+
+ final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter != null) {
+ mBtConnectionListener.init(btAdapter);
+ mBleScanner.init(context, btAdapter);
+ } else {
+ Log.w(TAG, "BluetoothAdapter is NOT available.");
+ }
+
+ mAssociationStore.registerLocalListener(this);
+ }
+
+ /**
+ * @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).
+ */
+ public boolean isDevicePresent(int associationId) {
+ return mReportedSelfManagedDevices.contains(associationId)
+ || mConnectedBtDevices.contains(associationId)
+ || mNearbyBleDevices.contains(associationId)
+ || mSimulated.contains(associationId);
+ }
+
+ /**
+ * @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.
+ */
+
+ public boolean isBtConnected(int associationId) {
+ return mConnectedBtDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device in BLE range and had already reported to the app.
+ */
+ public boolean isBlePresent(int associationId) {
+ return mNearbyBleDevices.contains(associationId);
+ }
+
+ /**
+ * @return whether the current device had been already reported by the simulator.
+ */
+ public boolean isSimulatePresent(int associationId) {
+ return mSimulated.contains(associationId);
+ }
+
+ /**
+ * Marks a "self-managed" device as connected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int) notifyDeviceAppeared()}
+ */
+ public void onSelfManagedDeviceConnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_APPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected.
+ *
+ * <p>
+ * Must ONLY be invoked by the
+ * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
+ * when an application invokes
+ * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int) notifyDeviceDisappeared()}
+ */
+ public void onSelfManagedDeviceDisconnected(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ /**
+ * Marks a "self-managed" device as disconnected when binderDied.
+ */
+ public void onSelfManagedDeviceReporterBinderDied(int associationId) {
+ onDevicePresenceEvent(mReportedSelfManagedDevices,
+ associationId, EVENT_SELF_MANAGED_DISAPPEARED);
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceConnected(int associationId) {
+ synchronized (mBtDisconnectedDevices) {
+ // A device is considered reconnected within 10 seconds if a pending BLE lost report is
+ // followed by a detected Bluetooth connection.
+ boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
+ if (isReconnected) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+
+ Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
+ + "associationId( " + associationId + " )");
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
+
+ // Stop the BLE scan if all devices report BT connected status and BLE was present.
+ if (canStopBleScan()) {
+ mBleScanner.stopScanIfNeeded();
+ }
+
+ }
+ }
+
+ @Override
+ public void onBluetoothCompanionDeviceDisconnected(int associationId) {
+ Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
+ + "associationId( " + associationId + " )");
+ // Start BLE scanning when the device is disconnected.
+ mBleScanner.startScan();
+
+ onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
+ // If current device is BLE present but BT is disconnected , means it will be
+ // potentially out of range later. Schedule BLE disappeared callback.
+ if (isBlePresent(associationId)) {
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.add(associationId);
+ }
+ mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
+ }
+ }
+
+ @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) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
+ if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
+ mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
+ }
+ }
+ }
+
+ @Override
+ public void onBleCompanionDeviceLost(int associationId) {
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
+ @TestApi
+ 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.
+ // No other caller (including SYSTEM!) should be allowed.
+ enforceCallerShellOrRoot();
+ // Make sure the association exists.
+ enforceAssociationExists(associationId);
+
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ simulateDeviceAppeared(associationId, event);
+ break;
+ case EVENT_BT_CONNECTED:
+ onBluetoothCompanionDeviceConnected(associationId);
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ simulateDeviceDisappeared(associationId, event);
+ break;
+ case EVENT_BT_DISCONNECTED:
+ onBluetoothCompanionDeviceDisconnected(associationId);
+ break;
+ default:
+ 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) {
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ }
+
+ private void simulateDeviceDisappeared(int associationId, int state) {
+ mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
+ onDevicePresenceEvent(mSimulated, associationId, state);
+ }
+
+ private void enforceAssociationExists(int associationId) {
+ if (mAssociationStore.getAssociationById(associationId) == null) {
+ throw new IllegalArgumentException(
+ "Association with id " + associationId + " does not exist.");
+ }
+ }
+
+ private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
+ int associationId, int event) {
+ Slog.i(TAG, "onDevicePresenceEvent() id=" + associationId + ", event=" + event);
+
+ switch (event) {
+ case EVENT_BLE_APPEARED:
+ synchronized (mBtDisconnectedDevices) {
+ // If a BLE device is detected within 10 seconds after BT is disconnected,
+ // flag it as BLE is present.
+ if (mBtDisconnectedDevices.contains(associationId)) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is present,"
+ + " do not need to send the callback with event ( "
+ + EVENT_BLE_APPEARED + " ).");
+ mBtDisconnectedDevicesBlePresence.append(associationId, true);
+ }
+ }
+ case EVENT_BT_CONNECTED:
+ case EVENT_SELF_MANAGED_APPEARED:
+ final boolean added = presentDevicesForSource.add(associationId);
+
+ if (!added) {
+ Slog.w(TAG, "Association with id "
+ + associationId + " is ALREADY reported as "
+ + "present by this source, event=" + event);
+ }
+
+ mCallback.onDeviceAppeared(associationId);
+
+ break;
+ case EVENT_BLE_DISAPPEARED:
+ case EVENT_BT_DISCONNECTED:
+ case EVENT_SELF_MANAGED_DISAPPEARED:
+ final boolean removed = presentDevicesForSource.remove(associationId);
+
+ if (!removed) {
+ Slog.w(TAG, "Association with id " + associationId + " was NOT reported "
+ + "as present by this source, event= " + event);
+
+ return;
+ }
+
+ mCallback.onDeviceDisappeared(associationId);
+
+ break;
+ default:
+ Slog.e(TAG, "Event: " + event + " is not supported");
+ return;
+ }
+
+ mCallback.onDevicePresenceEvent(associationId, event);
+ }
+
+ /**
+ * Implements
+ * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
+ */
+ @Override
+ public void onAssociationRemoved(@NonNull AssociationInfo association) {
+ final int id = association.getId();
+ if (DEBUG) {
+ Log.i(TAG, "onAssociationRemoved() id=" + id);
+ Log.d(TAG, " > association=" + association);
+ }
+
+ mConnectedBtDevices.remove(id);
+ mNearbyBleDevices.remove(id);
+ mReportedSelfManagedDevices.remove(id);
+ mSimulated.remove(id);
+ mBtDisconnectedDevices.remove(id);
+ mBtDisconnectedDevicesBlePresence.delete(id);
+
+ // Do NOT call mCallback.onDeviceDisappeared()!
+ // CompanionDeviceManagerService will know that the association is removed, and will do
+ // what's needed.
+ }
+
+ /**
+ * Return a set of devices that pending to report connectivity
+ */
+ public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
+ synchronized (mBtConnectionListener.mPendingConnectedDevices) {
+ return mBtConnectionListener.mPendingConnectedDevices;
+ }
+ }
+
+ private static void enforceCallerShellOrRoot() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
+
+ throw new SecurityException("Caller is neither Shell nor Root");
+ }
+
+ /**
+ * The BLE scan can be only stopped if all the devices have been reported
+ * BT connected and BLE presence and are not pending to report BLE lost.
+ */
+ private boolean canStopBleScan() {
+ for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
+ int id = ai.getId();
+ synchronized (mBtDisconnectedDevices) {
+ if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
+ && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
+ Slog.i(TAG, "The BLE scan cannot be stopped, "
+ + "device( " + id + " ) is not yet connected "
+ + "OR the BLE is not current present Or is pending to report BLE lost");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Dumps system information about devices that are marked as "present".
+ */
+ public void dump(@NonNull PrintWriter out) {
+ out.append("Companion Device Present: ");
+ if (mConnectedBtDevices.isEmpty()
+ && mNearbyBleDevices.isEmpty()
+ && mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ return;
+ } else {
+ out.append("\n");
+ }
+
+ out.append(" Connected Bluetooth Devices: ");
+ if (mConnectedBtDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mConnectedBtDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Nearby BLE Devices: ");
+ if (mNearbyBleDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mNearbyBleDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+
+ out.append(" Self-Reported Devices: ");
+ if (mReportedSelfManagedDevices.isEmpty()) {
+ out.append("<empty>\n");
+ } else {
+ out.append("\n");
+ for (int associationId : mReportedSelfManagedDevices) {
+ AssociationInfo a = mAssociationStore.getAssociationById(associationId);
+ out.append(" ").append(a.toShortString()).append('\n');
+ }
+ }
+ }
+
+ private class SimulatedDevicePresenceSchedulerHelper extends Handler {
+ SimulatedDevicePresenceSchedulerHelper() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ // First, unschedule if it was scheduled previously.
+ if (hasMessages(/* what */ associationId)) {
+ removeMessages(/* what */ associationId);
+ }
+
+ sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
+ }
+
+ void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
+ removeMessages(/* what */ associationId);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ if (mSimulated.contains(associationId)) {
+ onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
+ }
+ }
+ }
+
+ private class BleDeviceDisappearedScheduler extends Handler {
+ BleDeviceDisappearedScheduler() {
+ super(Looper.getMainLooper());
+ }
+
+ void scheduleBleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ removeMessages(associationId);
+ }
+ Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
+ sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
+ }
+
+ void unScheduleDeviceDisappeared(int associationId) {
+ if (hasMessages(associationId)) {
+ Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
+ synchronized (mBtDisconnectedDevices) {
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+
+ removeMessages(associationId);
+ }
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ final int associationId = msg.what;
+ synchronized (mBtDisconnectedDevices) {
+ final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
+ associationId);
+ // If a device hasn't reported after 10 seconds and is not currently present,
+ // assume BLE is lost and trigger the onDeviceEvent callback with the
+ // EVENT_BLE_DISAPPEARED event.
+ if (mBtDisconnectedDevices.contains(associationId)
+ && !isCurrentPresent) {
+ Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
+ + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
+ onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
+ }
+
+ mBtDisconnectedDevices.remove(associationId);
+ mBtDisconnectedDevicesBlePresence.delete(associationId);
+ }
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
deleted file mode 100644
index 2a933a8340c6..000000000000
--- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
+++ /dev/null
@@ -1,1042 +0,0 @@
-/*
- * Copyright (C) 2022 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.presence;
-
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
-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.companion.DevicePresenceEvent.NO_ASSOCIATION;
-import static android.os.Process.ROOT_UID;
-import static android.os.Process.SHELL_UID;
-
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanObserveDevicePresenceByUuid;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.annotation.TestApi;
-import android.annotation.UserIdInt;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.companion.AssociationInfo;
-import android.companion.DeviceNotAssociatedException;
-import android.companion.DevicePresenceEvent;
-import android.companion.ObservingDevicePresenceRequest;
-import android.content.Context;
-import android.hardware.power.Mode;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelUuid;
-import android.os.PowerManagerInternal;
-import android.os.RemoteException;
-import android.os.UserManager;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.association.AssociationStore;
-
-import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Class responsible for monitoring companion devices' "presence" status (i.e.
- * connected/disconnected for Bluetooth devices; nearby or not for BLE devices).
- *
- * <p>
- * Should only be used by
- * {@link com.android.server.companion.CompanionDeviceManagerService CompanionDeviceManagerService}
- * to which it provides the following API:
- * <ul>
- * <li> {@link #onSelfManagedDeviceConnected(int)}
- * <li> {@link #onSelfManagedDeviceDisconnected(int)}
- * <li> {@link #isDevicePresent(int)}
- * </ul>
- */
-@SuppressLint("LongLogTag")
-public class DevicePresenceProcessor implements AssociationStore.OnChangeListener,
- BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
- static final boolean DEBUG = false;
- private static final String TAG = "CDM_DevicePresenceProcessor";
-
- @NonNull
- private final Context mContext;
- @NonNull
- private final CompanionAppBinder mCompanionAppBinder;
- @NonNull
- private final AssociationStore mAssociationStore;
- @NonNull
- private final ObservableUuidStore mObservableUuidStore;
- @NonNull
- private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
- @NonNull
- private final BleCompanionDeviceScanner mBleScanner;
- @NonNull
- private final PowerManagerInternal mPowerManagerInternal;
-
- // NOTE: Same association may appear in more than one of the following sets at the same time.
- // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
- // companion applications, while at the same be connected via BT, or detected nearby by BLE
- // scanner)
- @NonNull
- private final Set<Integer> mConnectedBtDevices = new HashSet<>();
- @NonNull
- private final Set<Integer> mNearbyBleDevices = new HashSet<>();
- @NonNull
- private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
- @NonNull
- private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
- @NonNull
- @GuardedBy("mBtDisconnectedDevices")
- private final Set<Integer> mBtDisconnectedDevices = new HashSet<>();
-
- // A map to track device presence within 10 seconds of Bluetooth disconnection.
- // The key is the association ID, and the boolean value indicates if the device
- // was detected again within that time frame.
- @GuardedBy("mBtDisconnectedDevices")
- private final @NonNull SparseBooleanArray mBtDisconnectedDevicesBlePresence =
- new SparseBooleanArray();
-
- // Tracking "simulated" presence. Used for debugging and testing only.
- private final @NonNull Set<Integer> mSimulated = new HashSet<>();
- private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
- new SimulatedDevicePresenceSchedulerHelper();
-
- private final BleDeviceDisappearedScheduler mBleDeviceDisappearedScheduler =
- new BleDeviceDisappearedScheduler();
-
- public DevicePresenceProcessor(@NonNull Context context,
- @NonNull CompanionAppBinder companionAppBinder,
- UserManager userManager,
- @NonNull AssociationStore associationStore,
- @NonNull ObservableUuidStore observableUuidStore,
- @NonNull PowerManagerInternal powerManagerInternal) {
- mContext = context;
- mCompanionAppBinder = companionAppBinder;
- mAssociationStore = associationStore;
- mObservableUuidStore = observableUuidStore;
- mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
- associationStore, mObservableUuidStore,
- /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
- mBleScanner = new BleCompanionDeviceScanner(associationStore,
- /* BleCompanionDeviceScanner.Callback */ this);
- mPowerManagerInternal = powerManagerInternal;
- }
-
- /** Initialize {@link DevicePresenceProcessor} */
- public void init(Context context) {
- if (DEBUG) Slog.i(TAG, "init()");
-
- final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
- if (btAdapter != null) {
- mBtConnectionListener.init(btAdapter);
- mBleScanner.init(context, btAdapter);
- } else {
- Slog.w(TAG, "BluetoothAdapter is NOT available.");
- }
-
- mAssociationStore.registerLocalListener(this);
- }
-
- /**
- * Process device presence start request.
- */
- public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
- String packageName, int userId) {
- Slog.i(TAG,
- "Start observing request=[" + request + "] for userId=[" + userId + "], package=["
- + packageName + "]...");
- final ParcelUuid requestUuid = request.getUuid();
-
- if (requestUuid != null) {
- enforceCallerCanObserveDevicePresenceByUuid(mContext);
-
- // If it's already being observed, then no-op.
- if (mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
- Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
- + userId + "] is already being observed.");
- return;
- }
-
- final ObservableUuid observableUuid = new ObservableUuid(userId, requestUuid,
- packageName, System.currentTimeMillis());
- mObservableUuidStore.writeObservableUuid(userId, observableUuid);
- } else {
- final int associationId = request.getAssociationId();
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
-
- // If it's already being observed, then no-op.
- if (association.isNotifyOnDeviceNearby()) {
- Slog.i(TAG, "Associated device id=[" + association.getId()
- + "] is already being observed. No-op.");
- return;
- }
-
- association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(true)
- .build();
- mAssociationStore.updateAssociation(association);
-
- // Send callback immediately if the device is present.
- if (isDevicePresent(associationId)) {
- Slog.i(TAG, "Device is already present. Triggering callback.");
- if (isBlePresent(associationId)) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
- } else if (isBtConnected(associationId)) {
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
- } else if (isSimulatePresent(associationId)) {
- onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_APPEARED);
- }
- }
- }
-
- Slog.i(TAG, "Registered device presence listener.");
- }
-
- /**
- * Process device presence stop request.
- */
- public void stopObservingDevicePresence(ObservingDevicePresenceRequest request,
- String packageName, int userId) {
- Slog.i(TAG,
- "Stop observing request=[" + request + "] for userId=[" + userId + "], package=["
- + packageName + "]...");
-
- final ParcelUuid requestUuid = request.getUuid();
-
- if (requestUuid != null) {
- enforceCallerCanObserveDevicePresenceByUuid(mContext);
-
- if (!mObservableUuidStore.isUuidBeingObserved(requestUuid, userId, packageName)) {
- Slog.i(TAG, "UUID=[" + requestUuid + "], package=[" + packageName + "], userId=["
- + userId + "] is already not being observed.");
- return;
- }
-
- mObservableUuidStore.removeObservableUuid(userId, requestUuid, packageName);
- removeCurrentConnectedUuidDevice(requestUuid);
- } else {
- final int associationId = request.getAssociationId();
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
-
- // If it's already being observed, then no-op.
- if (!association.isNotifyOnDeviceNearby()) {
- Slog.i(TAG, "Associated device id=[" + association.getId()
- + "] is already not being observed. No-op.");
- return;
- }
-
- association = (new AssociationInfo.Builder(association)).setNotifyOnDeviceNearby(false)
- .build();
- mAssociationStore.updateAssociation(association);
- }
-
- Slog.i(TAG, "Unregistered device presence listener.");
-
- // If last listener is unregistered, then unbind application.
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppBinder.unbindCompanionApplication(userId, packageName);
- }
- }
-
- /**
- * For legacy device presence below Android V.
- *
- * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
- */
- @Deprecated
- public void startObservingDevicePresence(int userId, String packageName, String deviceAddress)
- throws RemoteException {
- Slog.i(TAG,
- "Start observing device=[" + deviceAddress + "] for userId=[" + userId
- + "], package=["
- + packageName + "]...");
-
- enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
-
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
- packageName, deviceAddress);
-
- if (association == null) {
- throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
- + " is not associated with device " + deviceAddress
- + " for user " + userId));
- }
-
- startObservingDevicePresence(
- new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
- .build(), packageName, userId);
- }
-
- /**
- * For legacy device presence below Android V.
- *
- * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String,
- * int)}
- */
- @Deprecated
- public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress)
- throws RemoteException {
- Slog.i(TAG,
- "Stop observing device=[" + deviceAddress + "] for userId=[" + userId
- + "], package=["
- + packageName + "]...");
-
- enforceCallerCanManageAssociationsForPackage(mContext, userId, packageName, null);
-
- AssociationInfo association = mAssociationStore.getFirstAssociationByAddress(userId,
- packageName, deviceAddress);
-
- if (association == null) {
- throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
- + " is not associated with device " + deviceAddress
- + " for user " + userId));
- }
-
- stopObservingDevicePresence(
- new ObservingDevicePresenceRequest.Builder().setAssociationId(association.getId())
- .build(), packageName, userId);
- }
-
- /**
- * @return whether the package should be bound (i.e. at least one of the devices associated with
- * 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.getActiveAssociationsByPackage(userId, packageName);
- final List<ObservableUuid> observableUuids =
- mObservableUuidStore.getObservableUuidsForPackage(userId, packageName);
-
- for (AssociationInfo association : packageAssociations) {
- if (!association.shouldBindWhenPresent()) continue;
- if (isDevicePresent(association.getId())) return true;
- }
-
- for (ObservableUuid uuid : observableUuids) {
- if (isDeviceUuidPresent(uuid.getUuid())) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Bind the system to the app if it's not bound.
- *
- * Set bindImportant to true when the association is self-managed to avoid the target service
- * being killed.
- */
- private void bindApplicationIfNeeded(int userId, String packageName, boolean bindImportant) {
- if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
- mCompanionAppBinder.bindCompanionApplication(
- userId, packageName, bindImportant, this::onBinderDied);
- } else {
- Slog.i(TAG,
- "UserId=[" + userId + "], packageName=[" + packageName + "] is already bound.");
- }
- }
-
- /**
- * @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).
- */
- public boolean isDevicePresent(int associationId) {
- return mReportedSelfManagedDevices.contains(associationId)
- || mConnectedBtDevices.contains(associationId)
- || mNearbyBleDevices.contains(associationId)
- || mSimulated.contains(associationId);
- }
-
- /**
- * @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.
- */
-
- public boolean isBtConnected(int associationId) {
- return mConnectedBtDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device in BLE range and had already reported to the app.
- */
- public boolean isBlePresent(int associationId) {
- return mNearbyBleDevices.contains(associationId);
- }
-
- /**
- * @return whether the current device had been already reported by the simulator.
- */
- public boolean isSimulatePresent(int associationId) {
- return mSimulated.contains(associationId);
- }
-
- /**
- * Marks a "self-managed" device as connected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService
- * CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceAppeared(int)
- * notifyDeviceAppeared()}
- */
- public void onSelfManagedDeviceConnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_APPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected.
- *
- * <p>
- * Must ONLY be invoked by the
- * {@link com.android.server.companion.CompanionDeviceManagerService
- * CompanionDeviceManagerService}
- * when an application invokes
- * {@link android.companion.CompanionDeviceManager#notifyDeviceDisappeared(int)
- * notifyDeviceDisappeared()}
- */
- public void onSelfManagedDeviceDisconnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- /**
- * Marks a "self-managed" device as disconnected when binderDied.
- */
- public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
- associationId, EVENT_SELF_MANAGED_DISAPPEARED);
- }
-
- @Override
- public void onBluetoothCompanionDeviceConnected(int associationId) {
- synchronized (mBtDisconnectedDevices) {
- // A device is considered reconnected within 10 seconds if a pending BLE lost report is
- // followed by a detected Bluetooth connection.
- boolean isReconnected = mBtDisconnectedDevices.contains(associationId);
- if (isReconnected) {
- Slog.i(TAG, "Device ( " + associationId + " ) is reconnected within 10s.");
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
-
- Slog.i(TAG, "onBluetoothCompanionDeviceConnected: "
- + "associationId( " + associationId + " )");
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_CONNECTED);
-
- // Stop the BLE scan if all devices report BT connected status and BLE was present.
- if (canStopBleScan()) {
- mBleScanner.stopScanIfNeeded();
- }
-
- }
- }
-
- @Override
- public void onBluetoothCompanionDeviceDisconnected(int associationId) {
- Slog.i(TAG, "onBluetoothCompanionDeviceDisconnected "
- + "associationId( " + associationId + " )");
- // Start BLE scanning when the device is disconnected.
- mBleScanner.startScan();
-
- onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
- // If current device is BLE present but BT is disconnected , means it will be
- // potentially out of range later. Schedule BLE disappeared callback.
- if (isBlePresent(associationId)) {
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.add(associationId);
- }
- mBleDeviceDisappearedScheduler.scheduleBleDeviceDisappeared(associationId);
- }
- }
-
-
- @Override
- public void onBleCompanionDeviceFound(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_APPEARED);
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(associationId);
- if (mBtDisconnectedDevices.contains(associationId) && isCurrentPresent) {
- mBleDeviceDisappearedScheduler.unScheduleDeviceDisappeared(associationId);
- }
- }
- }
-
- @Override
- public void onBleCompanionDeviceLost(int associationId) {
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
- @TestApi
- 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.
- // No other caller (including SYSTEM!) should be allowed.
- enforceCallerShellOrRoot();
- // Make sure the association exists.
- enforceAssociationExists(associationId);
-
- switch (event) {
- case EVENT_BLE_APPEARED:
- simulateDeviceAppeared(associationId, event);
- break;
- case EVENT_BT_CONNECTED:
- onBluetoothCompanionDeviceConnected(associationId);
- break;
- case EVENT_BLE_DISAPPEARED:
- simulateDeviceDisappeared(associationId, event);
- break;
- case EVENT_BT_DISCONNECTED:
- onBluetoothCompanionDeviceDisconnected(associationId);
- break;
- default:
- 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) {
- onDevicePresenceEvent(mSimulated, associationId, state);
- mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- }
-
- private void simulateDeviceDisappeared(int associationId, int state) {
- mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
- onDevicePresenceEvent(mSimulated, associationId, state);
- }
-
- private void enforceAssociationExists(int associationId) {
- if (mAssociationStore.getAssociationById(associationId) == null) {
- throw new IllegalArgumentException(
- "Association with id " + associationId + " does not exist.");
- }
- }
-
- private void onDevicePresenceEvent(@NonNull Set<Integer> presentDevicesForSource,
- int associationId, int eventType) {
- Slog.i(TAG,
- "onDevicePresenceEvent() id=[" + associationId + "], event=[" + eventType + "]...");
-
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
- if (association == null) {
- Slog.e(TAG, "Association doesn't exist.");
- return;
- }
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
- final DevicePresenceEvent event = new DevicePresenceEvent(associationId, eventType, null);
-
- if (eventType == EVENT_BLE_APPEARED) {
- synchronized (mBtDisconnectedDevices) {
- // If a BLE device is detected within 10 seconds after BT is disconnected,
- // flag it as BLE is present.
- if (mBtDisconnectedDevices.contains(associationId)) {
- Slog.i(TAG, "Device ( " + associationId + " ) is present,"
- + " do not need to send the callback with event ( "
- + EVENT_BLE_APPEARED + " ).");
- mBtDisconnectedDevicesBlePresence.append(associationId, true);
- }
- }
- }
-
- switch (eventType) {
- case EVENT_BLE_APPEARED:
- case EVENT_BT_CONNECTED:
- case EVENT_SELF_MANAGED_APPEARED:
- final boolean added = presentDevicesForSource.add(associationId);
- if (!added) {
- Slog.w(TAG, "The association is already present.");
- }
-
- if (association.shouldBindWhenPresent()) {
- bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
- } else {
- return;
- }
-
- if (association.isSelfManaged() || added) {
- notifyDevicePresenceEvent(userId, packageName, event);
- // Also send the legacy callback.
- legacyNotifyDevicePresenceEvent(association, true);
- }
- break;
- case EVENT_BLE_DISAPPEARED:
- case EVENT_BT_DISCONNECTED:
- case EVENT_SELF_MANAGED_DISAPPEARED:
- final boolean removed = presentDevicesForSource.remove(associationId);
- if (!removed) {
- Slog.w(TAG, "The association is already NOT present.");
- }
-
- if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
- Slog.e(TAG, "Package is not bound");
- return;
- }
-
- if (association.isSelfManaged() || removed) {
- notifyDevicePresenceEvent(userId, packageName, event);
- // Also send the legacy callback.
- legacyNotifyDevicePresenceEvent(association, false);
- }
-
- // Check if there are other devices associated to the app that are present.
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppBinder.unbindCompanionApplication(userId, packageName);
- }
- break;
- default:
- Slog.e(TAG, "Event: " + eventType + " is not supported.");
- break;
- }
- }
-
- @Override
- public void onDevicePresenceEventByUuid(ObservableUuid uuid, int eventType) {
- Slog.i(TAG, "onDevicePresenceEventByUuid ObservableUuid=[" + uuid + "], event=[" + eventType
- + "]...");
-
- final ParcelUuid parcelUuid = uuid.getUuid();
- final String packageName = uuid.getPackageName();
- final int userId = uuid.getUserId();
- final DevicePresenceEvent event = new DevicePresenceEvent(NO_ASSOCIATION, eventType,
- parcelUuid);
-
- switch (eventType) {
- case EVENT_BT_CONNECTED:
- boolean added = mConnectedUuidDevices.add(parcelUuid);
- if (!added) {
- Slog.w(TAG, "This device is already connected.");
- }
-
- bindApplicationIfNeeded(userId, packageName, false);
-
- notifyDevicePresenceEvent(userId, packageName, event);
- break;
- case EVENT_BT_DISCONNECTED:
- final boolean removed = mConnectedUuidDevices.remove(parcelUuid);
- if (!removed) {
- Slog.w(TAG, "This device is already disconnected.");
- return;
- }
-
- if (!mCompanionAppBinder.isCompanionApplicationBound(userId, packageName)) {
- Slog.e(TAG, "Package is not bound.");
- return;
- }
-
- notifyDevicePresenceEvent(userId, packageName, event);
-
- if (!shouldBindPackage(userId, packageName)) {
- mCompanionAppBinder.unbindCompanionApplication(userId, packageName);
- }
- break;
- default:
- Slog.e(TAG, "Event: " + eventType + " is not supported");
- break;
- }
- }
-
- /**
- * Notify device presence event to the app.
- *
- * @deprecated Use {@link #notifyDevicePresenceEvent(int, String, DevicePresenceEvent)} instead.
- */
- @Deprecated
- private void legacyNotifyDevicePresenceEvent(AssociationInfo association,
- boolean isAppeared) {
- Slog.i(TAG, "legacyNotifyDevicePresenceEvent() association=[" + association.toShortString()
- + "], isAppeared=[" + isAppeared + "]");
-
- final int userId = association.getUserId();
- final String packageName = association.getPackageName();
-
- final CompanionServiceConnector primaryServiceConnector =
- mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "Package is not bound.");
- return;
- }
-
- if (isAppeared) {
- primaryServiceConnector.postOnDeviceAppeared(association);
- } else {
- primaryServiceConnector.postOnDeviceDisappeared(association);
- }
- }
-
- /**
- * Notify the device presence event to the app.
- */
- private void notifyDevicePresenceEvent(int userId, String packageName,
- DevicePresenceEvent event) {
- Slog.i(TAG,
- "notifyCompanionDevicePresenceEvent userId=[" + userId + "], packageName=["
- + packageName + "], event=[" + event + "]...");
-
- final CompanionServiceConnector primaryServiceConnector =
- mCompanionAppBinder.getPrimaryServiceConnector(userId, packageName);
-
- if (primaryServiceConnector == null) {
- Slog.e(TAG, "Package is NOT bound.");
- return;
- }
-
- primaryServiceConnector.postOnDevicePresenceEvent(event);
- }
-
- /**
- * Notify the self-managed device presence event to the app.
- */
- public void notifySelfManagedDevicePresenceEvent(int associationId, boolean isAppeared) {
- Slog.i(TAG, "notifySelfManagedDeviceAppeared() id=" + associationId);
-
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- if (!association.isSelfManaged()) {
- throw new IllegalArgumentException("Association id=[" + associationId
- + "] is not self-managed.");
- }
- // AssociationInfo class is immutable: create a new AssociationInfo object with updated
- // timestamp.
- association = (new AssociationInfo.Builder(association))
- .setLastTimeConnected(System.currentTimeMillis())
- .build();
- mAssociationStore.updateAssociation(association);
-
- if (isAppeared) {
- onSelfManagedDeviceConnected(associationId);
- } else {
- onSelfManagedDeviceDisconnected(associationId);
- }
-
- final String deviceProfile = association.getDeviceProfile();
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- Slog.i(TAG, "Enable hint mode for device device profile: " + deviceProfile);
- mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, isAppeared);
- }
- }
-
- private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
- @NonNull CompanionServiceConnector serviceConnector) {
-
- boolean isPrimary = serviceConnector.isPrimary();
- Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);
-
- // First, disable hint mode for Auto profile and mark not BOUND for primary service ONLY.
- if (isPrimary) {
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
-
- for (AssociationInfo association : associations) {
- final String deviceProfile = association.getDeviceProfile();
- if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
- Slog.i(TAG, "Disable hint mode for device profile: " + deviceProfile);
- mPowerManagerInternal.setPowerMode(Mode.AUTOMOTIVE_PROJECTION, false);
- break;
- }
- }
-
- mCompanionAppBinder.removePackage(userId, packageName);
- }
-
- // Second: schedule rebinding if needed.
- final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);
-
- if (shouldScheduleRebind) {
- mCompanionAppBinder.scheduleRebinding(userId, packageName, serviceConnector);
- }
- }
-
- /**
- * Check if the system should rebind the self-managed secondary services
- * OR non-self-managed services.
- */
- private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
- // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
- // app is uninstalled.
- boolean stillAssociated = false;
- // 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.getActiveAssociationsByPackage(userId, packageName)) {
- final int associationId = ai.getId();
- stillAssociated = true;
- if (ai.isSelfManaged()) {
- // Do not rebind if primary one is died for selfManaged application.
- if (isPrimary && isDevicePresent(associationId)) {
- onSelfManagedDeviceReporterBinderDied(associationId);
- shouldScheduleRebind = false;
- }
- // Do not rebind if both primary and secondary services are died for
- // selfManaged application.
- shouldScheduleRebind = mCompanionAppBinder.isCompanionApplicationBound(userId,
- packageName);
- } else if (ai.isNotifyOnDeviceNearby()) {
- // Always rebind for non-selfManaged devices.
- shouldScheduleRebind = true;
- }
- }
-
- for (ObservableUuid uuid : uuids) {
- if (isDeviceUuidPresent(uuid.getUuid())) {
- shouldScheduleRebindForUuid = true;
- break;
- }
- }
-
- return (stillAssociated && shouldScheduleRebind) || shouldScheduleRebindForUuid;
- }
-
- /**
- * Implements
- * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)}
- */
- @Override
- public void onAssociationRemoved(@NonNull AssociationInfo association) {
- final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "onAssociationRemoved() id=" + id);
- Log.d(TAG, " > association=" + association);
- }
-
- mConnectedBtDevices.remove(id);
- mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
- mSimulated.remove(id);
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.remove(id);
- mBtDisconnectedDevicesBlePresence.delete(id);
- }
-
- // Do NOT call mCallback.onDeviceDisappeared()!
- // CompanionDeviceManagerService will know that the association is removed, and will do
- // what's needed.
- }
-
- /**
- * Return a set of devices that pending to report connectivity
- */
- public SparseArray<Set<BluetoothDevice>> getPendingConnectedDevices() {
- synchronized (mBtConnectionListener.mPendingConnectedDevices) {
- return mBtConnectionListener.mPendingConnectedDevices;
- }
- }
-
- private static void enforceCallerShellOrRoot() {
- final int callingUid = Binder.getCallingUid();
- if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
-
- throw new SecurityException("Caller is neither Shell nor Root");
- }
-
- /**
- * The BLE scan can be only stopped if all the devices have been reported
- * BT connected and BLE presence and are not pending to report BLE lost.
- */
- private boolean canStopBleScan() {
- for (AssociationInfo ai : mAssociationStore.getActiveAssociations()) {
- int id = ai.getId();
- synchronized (mBtDisconnectedDevices) {
- if (ai.isNotifyOnDeviceNearby() && !(isBtConnected(id)
- && isBlePresent(id) && mBtDisconnectedDevices.isEmpty())) {
- Slog.i(TAG, "The BLE scan cannot be stopped, "
- + "device( " + id + " ) is not yet connected "
- + "OR the BLE is not current present Or is pending to report BLE lost");
- return false;
- }
- }
- }
- return true;
- }
-
- /**
- * Dumps system information about devices that are marked as "present".
- */
- public void dump(@NonNull PrintWriter out) {
- out.append("Companion Device Present: ");
- if (mConnectedBtDevices.isEmpty()
- && mNearbyBleDevices.isEmpty()
- && mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- return;
- } else {
- out.append("\n");
- }
-
- out.append(" Connected Bluetooth Devices: ");
- if (mConnectedBtDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mConnectedBtDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Nearby BLE Devices: ");
- if (mNearbyBleDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mNearbyBleDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
-
- out.append(" Self-Reported Devices: ");
- if (mReportedSelfManagedDevices.isEmpty()) {
- out.append("<empty>\n");
- } else {
- out.append("\n");
- for (int associationId : mReportedSelfManagedDevices) {
- AssociationInfo a = mAssociationStore.getAssociationById(associationId);
- out.append(" ").append(a.toShortString()).append('\n');
- }
- }
- }
-
- private class SimulatedDevicePresenceSchedulerHelper extends Handler {
- SimulatedDevicePresenceSchedulerHelper() {
- super(Looper.getMainLooper());
- }
-
- void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- // First, unschedule if it was scheduled previously.
- if (hasMessages(/* what */ associationId)) {
- removeMessages(/* what */ associationId);
- }
-
- sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
- }
-
- void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
- removeMessages(/* what */ associationId);
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- if (mSimulated.contains(associationId)) {
- onDevicePresenceEvent(mSimulated, associationId, EVENT_BLE_DISAPPEARED);
- }
- }
- }
-
- private class BleDeviceDisappearedScheduler extends Handler {
- BleDeviceDisappearedScheduler() {
- super(Looper.getMainLooper());
- }
-
- void scheduleBleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- removeMessages(associationId);
- }
- Slog.i(TAG, "scheduleBleDeviceDisappeared for Device: ( " + associationId + " ).");
- sendEmptyMessageDelayed(associationId, 10 * 1000 /* 10 seconds */);
- }
-
- void unScheduleDeviceDisappeared(int associationId) {
- if (hasMessages(associationId)) {
- Slog.i(TAG, "unScheduleDeviceDisappeared for Device( " + associationId + " )");
- synchronized (mBtDisconnectedDevices) {
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
-
- removeMessages(associationId);
- }
- }
-
- @Override
- public void handleMessage(@NonNull Message msg) {
- final int associationId = msg.what;
- synchronized (mBtDisconnectedDevices) {
- final boolean isCurrentPresent = mBtDisconnectedDevicesBlePresence.get(
- associationId);
- // If a device hasn't reported after 10 seconds and is not currently present,
- // assume BLE is lost and trigger the onDeviceEvent callback with the
- // EVENT_BLE_DISAPPEARED event.
- if (mBtDisconnectedDevices.contains(associationId)
- && !isCurrentPresent) {
- Slog.i(TAG, "Device ( " + associationId + " ) is likely BLE out of range, "
- + "sending callback with event ( " + EVENT_BLE_DISAPPEARED + " )");
- onDevicePresenceEvent(mNearbyBleDevices, associationId, EVENT_BLE_DISAPPEARED);
- }
-
- mBtDisconnectedDevices.remove(associationId);
- mBtDisconnectedDevicesBlePresence.delete(associationId);
- }
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
index fa0f6bd92acb..db15da2922cf 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -300,18 +300,4 @@ public class ObservableUuidStore {
return readObservableUuidsFromCache(userId);
}
}
-
- /**
- * Check if a UUID is being observed by the package.
- */
- public boolean isUuidBeingObserved(ParcelUuid uuid, int userId, String packageName) {
- final List<ObservableUuid> uuidsBeingObserved = getObservableUuidsForPackage(userId,
- packageName);
- for (ObservableUuid observableUuid : uuidsBeingObserved) {
- if (observableUuid.getUuid().equals(uuid)) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 697ef87b5a12..793fb7ff74b1 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -46,6 +46,7 @@ import java.util.concurrent.Future;
@SuppressLint("LongLogTag")
public class CompanionTransportManager {
private static final String TAG = "CDM_CompanionTransportManager";
+ private static final boolean DEBUG = false;
private boolean mSecureTransportEnabled = true;
@@ -136,17 +137,11 @@ public class CompanionTransportManager {
}
}
- /**
- * Attach transport.
- */
- public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
- Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
-
- mAssociationStore.getAssociationWithCallerChecks(associationId);
-
+ public void attachSystemDataTransport(String packageName, int userId, int associationId,
+ ParcelFileDescriptor fd) {
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
- detachSystemDataTransport(associationId);
+ detachSystemDataTransport(packageName, userId, associationId);
}
// TODO: Implement new API to pass a PSK
@@ -154,18 +149,9 @@ public class CompanionTransportManager {
notifyOnTransportsChanged();
}
-
- Slog.i(TAG, "Transport attached.");
}
- /**
- * Detach transport.
- */
- public void detachSystemDataTransport(int associationId) {
- Slog.i(TAG, "Detaching transport for association id=[" + associationId + "]...");
-
- mAssociationStore.getAssociationWithCallerChecks(associationId);
-
+ public void detachSystemDataTransport(String packageName, int userId, int associationId) {
synchronized (mTransports) {
final Transport transport = mTransports.removeReturnOld(associationId);
if (transport == null) {
@@ -175,8 +161,6 @@ public class CompanionTransportManager {
transport.stop();
notifyOnTransportsChanged();
}
-
- Slog.i(TAG, "Transport detached.");
}
private void notifyOnTransportsChanged() {
@@ -323,7 +307,8 @@ public class CompanionTransportManager {
int associationId = transport.mAssociationId;
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
if (association != null) {
- detachSystemDataTransport(
+ detachSystemDataTransport(association.getPackageName(),
+ association.getUserId(),
association.getId());
}
}
diff --git a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
index d7e766eed209..2cf1f462a7d1 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -39,6 +39,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.content.Context;
@@ -207,7 +208,7 @@ public final class PermissionsUtils {
/**
* Require the caller to hold necessary permission to observe device presence by UUID.
*/
- public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
+ public 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 "
@@ -234,6 +235,23 @@ public final class PermissionsUtils {
return checkCallerCanManageCompanionDevice(context);
}
+ /**
+ * Check if CDM can trust the context to process the association.
+ */
+ @Nullable
+ public static AssociationInfo sanitizeWithCallerChecks(@NonNull Context context,
+ @Nullable AssociationInfo association) {
+ if (association == null) return null;
+
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ if (!checkCallerCanManageAssociationsForPackage(context, userId, packageName)) {
+ return null;
+ }
+
+ return association;
+ }
+
private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
try {
return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;