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