summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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/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/CompanionServiceConnector.java (renamed from services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java)63
-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, 1769 insertions, 1873 deletions
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 5e00b7a798d8..2c26389071ce 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.registerDevicePresenceListenerService(deviceAddress,
+ mService.legacyStartObservingDevicePresence(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.unregisterDevicePresenceListenerService(deviceAddress,
+ mService.legacyStopObservingDevicePresence(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.notifyDeviceAppeared(associationId);
+ mService.notifySelfManagedDeviceAppeared(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.notifyDeviceDisappeared(associationId);
+ mService.notifySelfManagedDeviceDisappeared(associationId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 57d59e5e5bf0..1b00f90e1fb3 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -59,12 +59,16 @@ interface ICompanionDeviceManager {
int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void registerDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
- int userId);
+ void legacyStartObservingDevicePresence(in String deviceAddress, in String callingPackage, int userId);
@EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
- void unregisterDevicePresenceListenerService(in String deviceAddress, in String callingPackage,
- int userId);
+ 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);
+
+ @EnforcePermission("REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE")
+ void stopObservingDevicePresence(in ObservingDevicePresenceRequest request, in String packageName, int userId);
boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId);
@@ -93,9 +97,11 @@ interface ICompanionDeviceManager {
@EnforcePermission("USE_COMPANION_TRANSPORTS")
void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
- void notifyDeviceAppeared(int associationId);
+ @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
+ void notifySelfManagedDeviceAppeared(int associationId);
- void notifyDeviceDisappeared(int associationId);
+ @EnforcePermission("REQUEST_COMPANION_SELF_MANAGED")
+ void notifySelfManagedDeviceDisappeared(int associationId);
PendingIntent buildPermissionTransferUserConsentIntent(String callingPackage, int userId,
int associationId);
@@ -135,10 +141,4 @@ 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
deleted file mode 100644
index 0a4148535451..000000000000
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ /dev/null
@@ -1,567 +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;
-
-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 712162b2d3b5..f4f6c13e74e4 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -20,15 +20,10 @@ 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;
@@ -42,13 +37,10 @@ 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;
@@ -64,7 +56,6 @@ 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;
@@ -79,7 +70,6 @@ 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;
@@ -91,7 +81,6 @@ 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;
@@ -118,7 +107,8 @@ 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.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.CompanionAppBinder;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -131,10 +121,7 @@ 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")
@@ -146,10 +133,6 @@ 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;
@@ -165,10 +148,11 @@ public class CompanionDeviceManagerService extends SystemService {
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final BackupRestoreProcessor mBackupRestoreProcessor;
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
- private final CompanionApplicationController mCompanionAppController;
+ private final DevicePresenceProcessor mDevicePresenceMonitor;
+ private final CompanionAppBinder mCompanionAppController;
private final CompanionTransportManager mTransportManager;
private final DisassociationProcessor mDisassociationProcessor;
+ private final InactiveAssociationsRemovalService mInactiveAssociationsRemovalService;
private final CrossDeviceSyncController mCrossDeviceSyncController;
public CompanionDeviceManagerService(Context context) {
@@ -185,7 +169,7 @@ public class CompanionDeviceManagerService extends SystemService {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
final AssociationDiskStore associationDiskStore = new AssociationDiskStore();
- mAssociationStore = new AssociationStore(userManager, associationDiskStore);
+ mAssociationStore = new AssociationStore(context, userManager, associationDiskStore);
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mObservableUuidStore = new ObservableUuidStore();
@@ -196,11 +180,11 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
mAssociationRequestsProcessor);
- mDevicePresenceMonitor = new CompanionDevicePresenceMonitor(userManager,
- mAssociationStore, mObservableUuidStore, mDevicePresenceCallback);
+ mCompanionAppController = new CompanionAppBinder(
+ context, mAssociationStore, mObservableUuidStore, mPowerManagerInternal);
- mCompanionAppController = new CompanionApplicationController(
- context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
+ mDevicePresenceMonitor = new DevicePresenceProcessor(context,
+ mCompanionAppController, userManager, mAssociationStore, mObservableUuidStore,
mPowerManagerInternal);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
@@ -209,6 +193,9 @@ public class CompanionDeviceManagerService extends SystemService {
mAssociationStore, mPackageManagerInternal, mDevicePresenceMonitor,
mCompanionAppController, mSystemDataTransferRequestStore, mTransportManager);
+ mInactiveAssociationsRemovalService = new InactiveAssociationsRemovalService(
+ mAssociationStore, mDisassociationProcessor);
+
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
@@ -302,181 +289,6 @@ 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) {
@@ -522,27 +334,8 @@ public class CompanionDeviceManagerService extends SystemService {
mBackupRestoreProcessor.restorePendingAssociations(userId, packageName);
}
- // Revoke associations if the selfManaged companion device does not connect for 3 months.
void removeInactiveSelfManagedAssociations() {
- 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);
- }
+ mInactiveAssociationsRemovalService.removeIdleSelfManagedAssociations();
}
public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@@ -679,24 +472,15 @@ 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);
- final AssociationInfo association =
- getAssociationWithCallerChecks(userId, packageName, deviceMacAddress);
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(userId, packageName, deviceMacAddress);
}
@Override
public void disassociate(int associationId) {
- Slog.i(TAG, "disassociate() associationId=" + associationId);
-
- final AssociationInfo association =
- getAssociationWithCallerChecks(associationId);
- mDisassociationProcessor.disassociate(association.getId());
+ mDisassociationProcessor.disassociate(associationId);
}
@Override
@@ -758,21 +542,25 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
+ @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void registerDevicePresenceListenerService(String deviceAddress,
- String callingPackage, int userId) throws RemoteException {
- registerDevicePresenceListenerService_enforcePermission();
- // TODO: take the userId into account.
- registerDevicePresenceListenerActive(callingPackage, deviceAddress, true);
+ public void legacyStartObservingDevicePresence(String deviceAddress, String callingPackage,
+ int userId) throws RemoteException {
+ legacyStartObservingDevicePresence_enforcePermission();
+
+ mDevicePresenceMonitor.startObservingDevicePresence(userId, callingPackage,
+ deviceAddress);
}
@Override
+ @Deprecated
@EnforcePermission(REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE)
- public void unregisterDevicePresenceListenerService(String deviceAddress,
- String callingPackage, int userId) throws RemoteException {
- unregisterDevicePresenceListenerService_enforcePermission();
- // TODO: take the userId into account.
- registerDevicePresenceListenerActive(callingPackage, deviceAddress, false);
+ public void legacyStopObservingDevicePresence(String deviceAddress, String callingPackage,
+ int userId) throws RemoteException {
+ legacyStopObservingDevicePresence_enforcePermission();
+
+ mDevicePresenceMonitor.stopObservingDevicePresence(userId, callingPackage,
+ deviceAddress);
}
@Override
@@ -780,7 +568,8 @@ public class CompanionDeviceManagerService extends SystemService {
public void startObservingDevicePresence(ObservingDevicePresenceRequest request,
String packageName, int userId) {
startObservingDevicePresence_enforcePermission();
- registerDevicePresenceListener(request, packageName, userId, /* active */ true);
+
+ mDevicePresenceMonitor.startObservingDevicePresence(request, packageName, userId);
}
@Override
@@ -788,80 +577,8 @@ 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();
- 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);
- }
+ mDevicePresenceMonitor.stopObservingDevicePresence(request, packageName, userId);
}
@Override
@@ -874,8 +591,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public boolean isPermissionTransferUserConsented(String packageName, int userId,
int associationId) {
- return mSystemDataTransferProcessor.isPermissionTransferUserConsented(packageName,
- userId, associationId);
+ return mSystemDataTransferProcessor.isPermissionTransferUserConsented(associationId);
}
@Override
@@ -891,8 +607,7 @@ public class CompanionDeviceManagerService extends SystemService {
ParcelFileDescriptor fd) {
attachSystemDataTransport_enforcePermission();
- getAssociationWithCallerChecks(associationId);
- mTransportManager.attachSystemDataTransport(packageName, userId, associationId, fd);
+ mTransportManager.attachSystemDataTransport(associationId, fd);
}
@Override
@@ -900,96 +615,56 @@ public class CompanionDeviceManagerService extends SystemService {
public void detachSystemDataTransport(String packageName, int userId, int associationId) {
detachSystemDataTransport_enforcePermission();
- getAssociationWithCallerChecks(associationId);
- mTransportManager.detachSystemDataTransport(packageName, userId, associationId);
+ mTransportManager.detachSystemDataTransport(associationId);
+ }
+
+ @Override
+ @EnforcePermission(MANAGE_COMPANION_DEVICES)
+ public void enableSecureTransport(boolean enabled) {
+ enableSecureTransport_enforcePermission();
+
+ mTransportManager.enableSecureTransport(enabled);
}
@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(MANAGE_COMPANION_DEVICES)
- public void enableSecureTransport(boolean enabled) {
- enableSecureTransport_enforcePermission();
- mTransportManager.enableSecureTransport(enabled);
- }
+ @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
+ public void notifySelfManagedDeviceAppeared(int associationId) {
+ notifySelfManagedDeviceAppeared_enforcePermission();
- @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);
- }
+ mDevicePresenceMonitor.notifySelfManagedDevicePresenceEvent(associationId, true);
}
@Override
- 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);
+ @EnforcePermission(REQUEST_COMPANION_SELF_MANAGED)
+ public void notifySelfManagedDeviceDisappeared(int associationId) {
+ notifySelfManagedDeviceDisappeared_enforcePermission();
- 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);
- }
+ mDevicePresenceMonitor.notifySelfManagedDevicePresenceEvent(associationId, false);
}
@Override
@@ -997,66 +672,6 @@ 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,
@@ -1070,7 +685,8 @@ public class CompanionDeviceManagerService extends SystemService {
}
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
- createNewAssociation(userId, packageName, macAddressObj, null, null, false);
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
+ null, null, null, false, null, null);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -1099,9 +715,7 @@ public class CompanionDeviceManagerService extends SystemService {
@Override
public void setAssociationTag(int associationId, String tag) {
- AssociationInfo association = getAssociationWithCallerChecks(associationId);
- association = (new AssociationInfo.Builder(association)).setTag(tag).build();
- mAssociationStore.updateAssociation(association);
+ mAssociationRequestsProcessor.setAssociationTag(associationId, tag);
}
@Override
@@ -1146,14 +760,6 @@ 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
*/
@@ -1169,8 +775,6 @@ 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)) {
@@ -1280,29 +884,6 @@ 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) {
@@ -1315,7 +896,7 @@ public class CompanionDeviceManagerService extends SystemService {
}
@Override
- public void onPackageModified(String packageName) {
+ public void onPackageModified(@NonNull String packageName) {
onPackageModifiedInternal(getChangingUserId(), packageName);
}
@@ -1325,28 +906,12 @@ 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 cdf832f8c788..e3b4c95a7dab 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java
@@ -28,11 +28,6 @@ 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/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index a7a73cb6bddb..a78938400a1e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,8 +18,6 @@ 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;
@@ -38,7 +36,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.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.transport.CompanionTransportManager;
@@ -51,7 +49,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
private final CompanionDeviceManagerService mService;
private final DisassociationProcessor mDisassociationProcessor;
private final AssociationStore mAssociationStore;
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final DevicePresenceProcessor mDevicePresenceProcessor;
private final CompanionTransportManager mTransportManager;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -60,7 +58,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore,
- CompanionDevicePresenceMonitor devicePresenceMonitor,
+ DevicePresenceProcessor devicePresenceProcessor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
AssociationRequestsProcessor associationRequestsProcessor,
@@ -68,7 +66,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
DisassociationProcessor disassociationProcessor) {
mService = service;
mAssociationStore = associationStore;
- mDevicePresenceMonitor = devicePresenceMonitor;
+ mDevicePresenceProcessor = devicePresenceProcessor;
mTransportManager = transportManager;
mSystemDataTransferProcessor = systemDataTransferProcessor;
mAssociationRequestsProcessor = associationRequestsProcessor;
@@ -85,7 +83,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
if ("simulate-device-event".equals(cmd) && Flags.devicePresence()) {
associationId = getNextIntArgRequired();
int event = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, event);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, event);
return 0;
}
@@ -97,7 +95,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
ObservableUuid observableUuid = new ObservableUuid(
userId, ParcelUuid.fromString(uuid), packageName,
System.currentTimeMillis());
- mDevicePresenceMonitor.simulateDeviceEventByUuid(observableUuid, event);
+ mDevicePresenceProcessor.simulateDeviceEventByUuid(observableUuid, event);
return 0;
}
@@ -124,8 +122,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
String address = getNextArgRequired();
String deviceProfile = getNextArg();
final MacAddress macAddress = MacAddress.fromString(address);
- mService.createNewAssociation(userId, packageName, macAddress,
- /* displayName= */ deviceProfile, deviceProfile, false);
+ mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
+ deviceProfile, deviceProfile, /* associatedDevice */ null, false,
+ /* callback */ null, /* resultReceiver */ null);
}
break;
@@ -134,8 +133,13 @@ class CompanionDeviceShellCommand extends ShellCommand {
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
- mService.getAssociationWithCallerChecks(userId, packageName, address);
- mDisassociationProcessor.disassociate(association.getId());
+ mAssociationStore.getFirstAssociationByAddress(userId, packageName,
+ address);
+ if (association == null) {
+ out.println("Association doesn't exist.");
+ } else {
+ mDisassociationProcessor.disassociate(association.getId());
+ }
}
break;
@@ -144,9 +148,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
final List<AssociationInfo> userAssociations =
mAssociationStore.getAssociationsByUser(userId);
for (AssociationInfo association : userAssociations) {
- if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
- mDisassociationProcessor.disassociate(association.getId());
- }
+ mDisassociationProcessor.disassociate(association.getId());
}
}
break;
@@ -157,12 +159,12 @@ class CompanionDeviceShellCommand extends ShellCommand {
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 0);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 0);
break;
case "simulate-device-disappeared":
associationId = getNextIntArgRequired();
- mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
+ mDevicePresenceProcessor.simulateDeviceEvent(associationId, /* event */ 1);
break;
case "get-backup-payload": {
@@ -410,10 +412,9 @@ 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(" clear-association-memory-cache");
+ pw.println(" refresh-cache");
pw.println(" Clear the in-memory association cache and reload all association ");
- pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
- pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+ pw.println(" information from disk. 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 a02d9f912bcd..a18776e67200 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -145,7 +145,8 @@ 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,
@@ -212,7 +213,8 @@ public class AssociationRequestsProcessor {
// 2b.4. Send the PendingIntent back to the app.
try {
callback.onAssociationPending(pendingIntent);
- } catch (RemoteException ignore) { }
+ } catch (RemoteException ignore) {
+ }
}
/**
@@ -252,7 +254,8 @@ public class AssociationRequestsProcessor {
// forward it back to the application via the callback.
try {
callback.onFailure(e.getMessage());
- } catch (RemoteException ignore) { }
+ } catch (RemoteException ignore) {
+ }
return;
}
@@ -322,7 +325,8 @@ public class AssociationRequestsProcessor {
* Enable system data sync.
*/
public void enableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
.setSystemDataSyncFlags(association.getSystemDataSyncFlags() | flags).build();
mAssociationStore.updateAssociation(updated);
@@ -332,12 +336,23 @@ public class AssociationRequestsProcessor {
* Disable system data sync.
*/
public void disableSystemDataSync(int associationId, int flags) {
- AssociationInfo association = mAssociationStore.getAssociationById(associationId);
+ AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ 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) {
@@ -396,14 +411,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 edebb55233d0..ae2b70852a35 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -18,6 +18,7 @@ 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;
@@ -26,6 +27,7 @@ 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;
@@ -57,21 +59,22 @@ 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.
@@ -100,25 +103,30 @@ 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 Object mLock = new Object();
-
+ private final Context mContext;
+ private final UserManager mUserManager;
+ private final AssociationDiskStore mDiskStore;
private final ExecutorService mExecutor;
+ private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mPersisted = false;
@GuardedBy("mLock")
@@ -132,10 +140,9 @@ public class AssociationStore {
private final RemoteCallbackList<IOnAssociationsChangedListener> mRemoteListeners =
new RemoteCallbackList<>();
- private final UserManager mUserManager;
- private final AssociationDiskStore mDiskStore;
-
- public AssociationStore(UserManager userManager, AssociationDiskStore diskStore) {
+ public AssociationStore(Context context, UserManager userManager,
+ AssociationDiskStore diskStore) {
+ mContext = context;
mUserManager = userManager;
mDiskStore = diskStore;
mExecutor = Executors.newSingleThreadExecutor();
@@ -202,7 +209,7 @@ public class AssociationStore {
synchronized (mLock) {
if (mIdToAssociationMap.containsKey(id)) {
- Slog.e(TAG, "Association with id=[" + id + "] already exists.");
+ Slog.e(TAG, "Association id=[" + id + "] already exists.");
return;
}
@@ -449,6 +456,26 @@ 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 ec8977918c56..20de1210dd9d 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -33,18 +33,19 @@ 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.CompanionDevicePresenceMonitor;
+import com.android.server.companion.presence.CompanionAppBinder;
+import com.android.server.companion.presence.DevicePresenceProcessor;
import com.android.server.companion.transport.CompanionTransportManager;
/**
- * A class response for Association removal.
+ * This class responsible for disassociation.
*/
@SuppressLint("LongLogTag")
public class DisassociationProcessor {
private static final String TAG = "CDM_DisassociationProcessor";
+
@NonNull
private final Context mContext;
@NonNull
@@ -52,11 +53,11 @@ public class DisassociationProcessor {
@NonNull
private final PackageManagerInternal mPackageManagerInternal;
@NonNull
- private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
+ private final DevicePresenceProcessor mDevicePresenceMonitor;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
- private final CompanionApplicationController mCompanionAppController;
+ private final CompanionAppBinder mCompanionAppController;
@NonNull
private final CompanionTransportManager mTransportManager;
private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
@@ -66,8 +67,8 @@ public class DisassociationProcessor {
@NonNull ActivityManager activityManager,
@NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
- @NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
- @NonNull CompanionApplicationController applicationController,
+ @NonNull DevicePresenceProcessor devicePresenceMonitor,
+ @NonNull CompanionAppBinder applicationController,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull CompanionTransportManager companionTransportManager) {
mContext = context;
@@ -89,11 +90,7 @@ public class DisassociationProcessor {
public void disassociate(int id) {
Slog.i(TAG, "Disassociating id=[" + id + "]...");
- final AssociationInfo association = mAssociationStore.getAssociationById(id);
- if (association == null) {
- Slog.e(TAG, "Can't disassociate id=[" + id + "]. It doesn't exist.");
- return;
- }
+ final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -118,12 +115,12 @@ public class DisassociationProcessor {
return;
}
+ // Detach transport if exists
+ mTransportManager.detachSystemDataTransport(id);
+
// Association cleanup.
- mAssociationStore.removeAssociation(association.getId());
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, id);
-
- // Detach transport if exists
- mTransportManager.detachSystemDataTransport(packageName, userId, id);
+ mAssociationStore.removeAssociation(association.getId());
// 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.
@@ -147,6 +144,24 @@ 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(() -> {
@@ -163,7 +178,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 f28731548dcc..b52904aa5301 100644
--- a/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -22,15 +22,14 @@ 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 idle self-managed associations.
+ * A Job Service responsible for clean up self-managed associations if it's idle for 90 days.
*
* 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}
@@ -41,14 +40,25 @@ 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");
- // Special policy for selfManaged that need to revoke associations if the device
- // does not connect for 90 days.
- LocalServices.getService(CompanionDeviceManagerServiceInternal.class)
- .removeInactiveSelfManagedAssociations();
+
+ removeIdleSelfManagedAssociations();
+
jobFinished(params, false);
return true;
}
@@ -77,4 +87,29 @@ 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 c5ca0bf7e9c5..9069689ee5eb 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -31,7 +31,6 @@ 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;
@@ -56,7 +55,6 @@ 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;
@@ -120,28 +118,10 @@ 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(String packageName, @UserIdInt int userId,
- int associationId) {
- resolveAssociation(packageName, userId, associationId);
+ public boolean isPermissionTransferUserConsented(int associationId) {
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
if (request == null) {
@@ -167,7 +147,8 @@ public class SystemDataTransferProcessor {
return null;
}
- final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
+ final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
+ associationId);
Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ "] associationId [" + associationId + "]");
@@ -207,7 +188,7 @@ public class SystemDataTransferProcessor {
Slog.i(LOG_TAG, "Start system data transfer for package [" + packageName
+ "] userId [" + userId + "] associationId [" + associationId + "]");
- final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
// Check if the request has been consented by the user.
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
@@ -239,24 +220,20 @@ public class SystemDataTransferProcessor {
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(true);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ int userId = mAssociationStore.getAssociationWithCallerChecks(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) {
- Binder.withCleanCallingIdentity(() -> {
- int userId = mAssociationStore.getAssociationById(associationId).getUserId();
- PermissionSyncRequest request = new PermissionSyncRequest(associationId);
- request.setUserConsented(false);
- mSystemDataTransferRequestStore.writeRequest(userId, request);
- });
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
+ PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+ request.setUserConsented(false);
+ mSystemDataTransferRequestStore.writeRequest(userId, request);
}
/**
@@ -264,18 +241,17 @@ public class SystemDataTransferProcessor {
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- 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;
- }
+ 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 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 c89ce11c169d..9c37881499bd 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.CompanionDevicePresenceMonitor.DEBUG;
+import static com.android.server.companion.presence.DevicePresenceProcessor.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 cb363a7c9d7f..2d345c48a8eb 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.CompanionDevicePresenceMonitor.DEBUG;
+import static com.android.server.companion.presence.DevicePresenceProcessor.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
new file mode 100644
index 000000000000..4ba4e2ce6899
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
@@ -0,0 +1,392 @@
+/*
+ * 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
deleted file mode 100644
index 7a1a83f53315..000000000000
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ /dev/null
@@ -1,620 +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.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/CompanionDeviceServiceConnector.java b/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
index 5abdb42b34fc..c01c3195e04d 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.presence;
import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
@@ -33,36 +33,42 @@ import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
-import android.util.Log;
+import android.util.Slog;
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")
-class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
- private static final String TAG = "CDM_CompanionServiceConnector";
- private static final boolean DEBUG = false;
+public class CompanionServiceConnector extends ServiceConnector.Impl<ICompanionDeviceService> {
- /* Unbinding before executing the callbacks can cause problems. Wait 5-seconds before unbind. */
- private static final long UNBIND_POST_DELAY_MS = 5_000;
-
- /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector} */
- interface Listener {
+ /** 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 CompanionDeviceServiceConnector serviceConnector);
+ @NonNull CompanionServiceConnector serviceConnector);
}
- private final @UserIdInt int mUserId;
- private final @NonNull ComponentName mComponentName;
+ private static final String TAG = "CDM_CompanionServiceConnector";
+
+ /* 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;
// 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.
- private @Nullable Listener mListener;
- private boolean mIsPrimary;
+ @Nullable
+ private Listener mListener;
/**
* Create a CompanionDeviceServiceConnector instance.
@@ -79,16 +85,16 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
* IMPORTANCE_FOREGROUND_SERVICE = 125. In order to kill the one time permission session, the
* service importance level should be higher than 125.
*/
- static CompanionDeviceServiceConnector newInstance(@NonNull Context context,
+ static CompanionServiceConnector 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 CompanionDeviceServiceConnector(
+ return new CompanionServiceConnector(
context, userId, componentName, bindingFlags, isPrimary);
}
- private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
+ private CompanionServiceConnector(@NonNull Context context, @UserIdInt int userId,
@NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) {
super(context, buildIntent(componentName), bindingFlags, userId, null);
mUserId = userId;
@@ -133,6 +139,7 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
return mIsPrimary;
}
+ @NonNull
ComponentName getComponentName() {
return mComponentName;
}
@@ -140,17 +147,15 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
@Override
protected void onServiceConnectionStatusChanged(
@NonNull ICompanionDeviceService service, boolean isConnected) {
- if (DEBUG) {
- Log.d(TAG, "onServiceConnection_StatusChanged() " + mComponentName.toShortString()
- + " connected=" + isConnected);
- }
+ Slog.d(TAG, "onServiceConnectionStatusChanged() " + mComponentName.toShortString()
+ + " connected=" + isConnected);
}
@Override
public void binderDied() {
super.binderDied();
- if (DEBUG) Log.d(TAG, "binderDied() " + mComponentName.toShortString());
+ Slog.d(TAG, "binderDied() " + mComponentName.toShortString());
// Handle primary process being killed
if (mListener != null) {
@@ -172,7 +177,8 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
* within system_server and thus tends to get heavily congested)
*/
@Override
- protected @NonNull Handler getJobHandler() {
+ @NonNull
+ protected Handler getJobHandler() {
return getServiceThread().getThreadHandler();
}
@@ -182,12 +188,14 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
return -1;
}
- private static @NonNull Intent buildIntent(@NonNull ComponentName componentName) {
+ @NonNull
+ private static Intent buildIntent(@NonNull ComponentName componentName) {
return new Intent(CompanionDeviceService.SERVICE_INTERFACE)
.setComponent(componentName);
}
- private static @NonNull ServiceThread getServiceThread() {
+ @NonNull
+ private static ServiceThread getServiceThread() {
if (sServiceThread == null) {
synchronized (CompanionDeviceManagerService.class) {
if (sServiceThread == null) {
@@ -206,5 +214,6 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
* <p>
* Do NOT reference directly, use {@link #getServiceThread()} method instead.
*/
- private static volatile @Nullable ServiceThread sServiceThread;
+ @Nullable
+ private static volatile ServiceThread sServiceThread;
}
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
new file mode 100644
index 000000000000..2a933a8340c6
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
@@ -0,0 +1,1042 @@
+/*
+ * 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 db15da2922cf..fa0f6bd92acb 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
@@ -300,4 +300,18 @@ 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 793fb7ff74b1..697ef87b5a12 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -46,7 +46,6 @@ 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;
@@ -137,11 +136,17 @@ public class CompanionTransportManager {
}
}
- public void attachSystemDataTransport(String packageName, int userId, int associationId,
- ParcelFileDescriptor fd) {
+ /**
+ * Attach transport.
+ */
+ public void attachSystemDataTransport(int associationId, ParcelFileDescriptor fd) {
+ Slog.i(TAG, "Attaching transport for association id=[" + associationId + "]...");
+
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
synchronized (mTransports) {
if (mTransports.contains(associationId)) {
- detachSystemDataTransport(packageName, userId, associationId);
+ detachSystemDataTransport(associationId);
}
// TODO: Implement new API to pass a PSK
@@ -149,9 +154,18 @@ public class CompanionTransportManager {
notifyOnTransportsChanged();
}
+
+ Slog.i(TAG, "Transport attached.");
}
- public void detachSystemDataTransport(String packageName, int userId, int associationId) {
+ /**
+ * Detach transport.
+ */
+ public void detachSystemDataTransport(int associationId) {
+ Slog.i(TAG, "Detaching transport for association id=[" + associationId + "]...");
+
+ mAssociationStore.getAssociationWithCallerChecks(associationId);
+
synchronized (mTransports) {
final Transport transport = mTransports.removeReturnOld(associationId);
if (transport == null) {
@@ -161,6 +175,8 @@ public class CompanionTransportManager {
transport.stop();
notifyOnTransportsChanged();
}
+
+ Slog.i(TAG, "Transport detached.");
}
private void notifyOnTransportsChanged() {
@@ -307,8 +323,7 @@ public class CompanionTransportManager {
int associationId = transport.mAssociationId;
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
if (association != null) {
- detachSystemDataTransport(association.getPackageName(),
- association.getUserId(),
+ detachSystemDataTransport(
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 2cf1f462a7d1..d7e766eed209 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -39,7 +39,6 @@ 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;
@@ -208,7 +207,7 @@ public final class PermissionsUtils {
/**
* Require the caller to hold necessary permission to observe device presence by UUID.
*/
- public static void enforceCallerCanObservingDevicePresenceByUuid(@NonNull Context context) {
+ public static void enforceCallerCanObserveDevicePresenceByUuid(@NonNull Context context) {
if (context.checkCallingPermission(REQUEST_OBSERVE_DEVICE_UUID_PRESENCE)
!= PERMISSION_GRANTED) {
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
@@ -235,23 +234,6 @@ 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;