diff options
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; |