From aa15e1cc90e5ef291a23316a83943c0ca4652c79 Mon Sep 17 00:00:00 2001 From: Sergey Nikolaienkov Date: Tue, 11 Jan 2022 09:37:54 +0100 Subject: [PATCH] [8/8] Use PresenceController and AppController in CdmService Use CompanionDevicePresenceMonitor and CompanionApplicationController in CompanionDeviceManagerService. Remove CompanionDevicePresenceController. Bug: 211398735 Test: atest CtsCompanionDeviceManagerCoreTestCases Test: atest CtsCompanionDeviceManagerUiAutomationTestCases Test: atest CtsOsTestCases:CompanionDeviceManagerTest Change-Id: I542c6d8157b51fc9c0a2e55055792b6951f292d9 --- .../android/companion/AssociationInfo.java | 12 + .../companion/AssociationCleanUpService.java | 17 +- .../CompanionDeviceManagerService.java | 916 ++++++------------ .../CompanionDevicePresenceController.java | 238 ----- .../server/companion/PackageUtils.java | 4 +- .../server/companion/PermissionsUtils.java | 14 + .../android/server/companion/RolesUtils.java | 2 +- 7 files changed, 346 insertions(+), 857 deletions(-) delete mode 100644 services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 373a8d957282a..f7f0235cd5088 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -207,6 +207,18 @@ public final class AssociationInfo implements Parcelable { return macAddress.equals(mDeviceMacAddress); } + /** + * Utility method to be used by CdmService only. + * + * @return whether CdmService should bind the companion application that "owns" this association + * when the device is present. + * + * @hide + */ + public boolean shouldBindWhenPresent() { + return mNotifyOnDeviceNearby || mSelfManaged; + } + /** @hide */ public @NonNull String toShortString() { final StringBuilder sb = new StringBuilder(); diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java index 0509e0cf5ccc7..55246e14d5920 100644 --- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java +++ b/services/companion/java/com/android/server/companion/AssociationCleanUpService.java @@ -16,7 +16,9 @@ package com.android.server.companion; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; + +import static java.util.concurrent.TimeUnit.DAYS; import android.app.job.JobInfo; import android.app.job.JobParameters; @@ -37,17 +39,16 @@ import com.android.server.LocalServices; */ public class AssociationCleanUpService extends JobService { private static final int JOB_ID = AssociationCleanUpService.class.hashCode(); - private static final long ONE_DAY_INTERVAL = 3 * 24 * 60 * 60 * 1000; // 1 Day - private CompanionDeviceManagerServiceInternal mCdmServiceInternal = LocalServices.getService( - CompanionDeviceManagerServiceInternal.class); + private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1); @Override public boolean onStartJob(final JobParameters params) { - Slog.i(LOG_TAG, "Execute the Association CleanUp job"); + Slog.i(TAG, "Execute the Association CleanUp job"); // Special policy for APP_STREAMING role that need to revoke associations if the device // does not connect for 3 months. AsyncTask.execute(() -> { - mCdmServiceInternal.associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); + LocalServices.getService(CompanionDeviceManagerServiceInternal.class) + .associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); jobFinished(params, false); }); return true; @@ -55,7 +56,7 @@ public class AssociationCleanUpService extends JobService { @Override public boolean onStopJob(final JobParameters params) { - Slog.i(LOG_TAG, "Association cleanup job stopped; id=" + params.getJobId() + Slog.i(TAG, "Association cleanup job stopped; id=" + params.getJobId() + ", reason=" + JobParameters.getInternalReasonCodeDescription( params.getInternalStopReasonCode())); @@ -63,7 +64,7 @@ public class AssociationCleanUpService extends JobService { } static void schedule(Context context) { - Slog.i(LOG_TAG, "Scheduling the Association Cleanup job"); + Slog.i(TAG, "Scheduling the Association Cleanup job"); final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); final JobInfo job = new JobInfo.Builder(JOB_ID, new ComponentName(context, AssociationCleanUpService.class)) diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index cef0e83f6006e..eaa99f74e24ee 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -18,31 +18,28 @@ package com.android.server.companion; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; -import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; -import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; import static android.content.pm.PackageManager.CERT_INPUT_SHA256; -import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.Binder.getCallingUid; import static android.os.Process.SYSTEM_UID; import static android.os.UserHandle.getCallingUserId; import static com.android.internal.util.CollectionUtils.any; -import static com.android.internal.util.CollectionUtils.find; import static com.android.internal.util.Preconditions.checkState; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; -import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable; import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED; -import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage; +import static com.android.server.companion.PackageUtils.enforceUsesCompanionDeviceFeature; +import static com.android.server.companion.PackageUtils.getPackageInfo; import static com.android.server.companion.PermissionsUtils.checkCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageAssociationsForPackage; import static com.android.server.companion.PermissionsUtils.enforceCallerCanManageCompanionDevice; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr; import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId; +import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks; import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation; import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MINUTES; import android.annotation.NonNull; @@ -53,26 +50,15 @@ import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.le.BluetoothLeScanner; -import android.bluetooth.le.ScanCallback; -import android.bluetooth.le.ScanFilter; -import android.bluetooth.le.ScanResult; -import android.bluetooth.le.ScanSettings; import android.companion.AssociationInfo; import android.companion.AssociationRequest; import android.companion.DeviceNotAssociatedException; import android.companion.IAssociationRequestCallback; import android.companion.ICompanionDeviceManager; import android.companion.IOnAssociationsChangedListener; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.pm.FeatureInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; @@ -93,11 +79,10 @@ import android.os.ServiceManager; import android.os.ShellCallback; import android.os.UserHandle; import android.os.UserManager; -import android.permission.PermissionControllerManager; import android.text.BidiFormatter; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.ExceptionUtils; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -112,86 +97,49 @@ import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; import com.android.server.pm.UserManagerInternal; -import com.android.server.wm.ActivityTaskManagerInternal; import java.io.File; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.TimeZone; -/** @hide */ @SuppressLint("LongLogTag") -public class CompanionDeviceManagerService extends SystemService - implements AssociationStore.OnChangeListener { - static final String LOG_TAG = "CompanionDeviceManagerService"; +public class CompanionDeviceManagerService extends SystemService { + static final String TAG = "CompanionDeviceManagerService"; static final boolean DEBUG = false; /** Range of Association IDs allocated for a user.*/ - static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; - - private static final long DEVICE_DISAPPEARED_TIMEOUT_MS = 10 * 1000; - private static final long DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS = 10 * 60 * 1000; - - static final long DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS = 10 * 1000; - + private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000; private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min 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 long ASSOCIATION_CLEAN_UP_TIME_WINDOW = - 90L * 24 * 60 * 60 * 1000; // 3 months + private static final long ASSOCIATION_CLEAN_UP_TIME_WINDOW = DAYS.toMillis(3 * 30); // 3 months - private static DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - static { - sDateFormat.setTimeZone(TimeZone.getDefault()); - } - - // Persistent data store for all Associations. private PersistentDataStore mPersistentStore; - private final AssociationStoreImpl mAssociationStore = new AssociationStoreImpl(); + private final PersistUserStateHandler mUserPersistenceHandler; + + private final AssociationStoreImpl mAssociationStore; private AssociationRequestsProcessor mAssociationRequestsProcessor; + private CompanionDevicePresenceMonitor mDevicePresenceMonitor; + private CompanionApplicationController mCompanionAppController; - private PowerWhitelistManager mPowerWhitelistManager; - private IAppOpsService mAppOpsManager; - private BluetoothAdapter mBluetoothAdapter; - private UserManager mUserManager; - - private ScanCallback mBleScanCallback = new BleScanCallback(); - PermissionControllerManager mPermissionControllerManager; - - private BluetoothDeviceConnectedListener mBluetoothDeviceConnectedListener = - new BluetoothDeviceConnectedListener(); - private BleStateBroadcastReceiver mBleStateBroadcastReceiver = new BleStateBroadcastReceiver(); - private List mCurrentlyConnectedDevices = new ArrayList<>(); - Set mPresentSelfManagedDevices = new HashSet<>(); - private ArrayMap mDevicesLastNearby = new ArrayMap<>(); - private UnbindDeviceListenersRunnable - mUnbindDeviceListenersRunnable = new UnbindDeviceListenersRunnable(); - private ArrayMap mTriggerDeviceDisappearedRunnables = - new ArrayMap<>(); - private final RemoteCallbackList mListeners = - new RemoteCallbackList<>(); - private final CompanionDeviceManagerServiceInternal mLocalService = new LocalService(this); - - final Handler mMainHandler = Handler.getMain(); - private final PersistUserStateHandler mUserPersistenceHandler = new PersistUserStateHandler(); - private CompanionDevicePresenceController mCompanionDevicePresenceController; + private final ActivityManagerInternal mAmInternal; + private final IAppOpsService mAppOpsManager; + private final PowerWhitelistManager mPowerWhitelistManager; + private final UserManager mUserManager; + final PackageManagerInternal mPackageManagerInternal; /** - * A structure that consist of two nested maps, and effectively maps (userId + packageName) to + * A structure that consists of two nested maps, and effectively maps (userId + packageName) to * a list of IDs that have been previously assigned to associations for that package. * We maintain this structure so that we never re-use association IDs for the same package * (until it's uninstalled). @@ -199,9 +147,8 @@ public class CompanionDeviceManagerService extends SystemService @GuardedBy("mPreviouslyUsedIds") private final SparseArray>> mPreviouslyUsedIds = new SparseArray<>(); - ActivityTaskManagerInternal mAtmInternal; - ActivityManagerInternal mAmInternal; - PackageManagerInternal mPackageManagerInternal; + private final RemoteCallbackList mListeners = + new RemoteCallbackList<>(); public CompanionDeviceManagerService(Context context) { super(context); @@ -209,14 +156,12 @@ public class CompanionDeviceManagerService extends SystemService mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class); mAppOpsManager = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); - mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); mAmInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); - mPermissionControllerManager = requireNonNull( - context.getSystemService(PermissionControllerManager.class)); mUserManager = context.getSystemService(UserManager.class); - LocalServices.addService(CompanionDeviceManagerServiceInternal.class, mLocalService); + mUserPersistenceHandler = new PersistUserStateHandler(); + mAssociationStore = new AssociationStoreImpl(); } @Override @@ -224,14 +169,24 @@ public class CompanionDeviceManagerService extends SystemService mPersistentStore = new PersistentDataStore(); loadAssociationsFromDisk(); - mAssociationStore.registerListener(this); + mAssociationStore.registerListener(mAssociationStoreChangeListener); - mCompanionDevicePresenceController = new CompanionDevicePresenceController(this); - mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mAssociationStore); + mDevicePresenceMonitor = new CompanionDevicePresenceMonitor( + mAssociationStore, mDevicePresenceCallback); - // Publish "binder service" + mAssociationRequestsProcessor = new AssociationRequestsProcessor( + /* cdmService */this, mAssociationStore); + + final Context context = getContext(); + mCompanionAppController = new CompanionApplicationController( + context, mApplicationControllerCallback); + + // Publish "binder" service. final CompanionDeviceManagerImpl impl = new CompanionDeviceManagerImpl(); publishBinderService(Context.COMPANION_DEVICE_SERVICE, impl); + + // Publish "local" service. + LocalServices.addService(CompanionDeviceManagerServiceInternal.class, new LocalService()); } void loadAssociationsFromDisk() { @@ -248,21 +203,13 @@ public class CompanionDeviceManagerService extends SystemService @Override public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { - registerPackageMonitor(); - - // Init Bluetooth - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - if (mBluetoothAdapter != null) { - mBluetoothAdapter.registerBluetoothConnectionCallback( - getContext().getMainExecutor(), - mBluetoothDeviceConnectedListener); - getContext().registerReceiver( - mBleStateBroadcastReceiver, mBleStateBroadcastReceiver.mIntentFilter); - initBleScanning(); - } else { - Slog.w(LOG_TAG, "No BluetoothAdapter available"); - } + final Context context = getContext(); + if (phase == PHASE_SYSTEM_SERVICES_READY) { + // WARNING: moving PackageMonitor to another thread (Looper) may introduce significant + // delays (even in case of the Main Thread). It may be fine overall, but would require + // updating the tests (adding a delay there). + mPackageMonitor.register(context, FgThread.get().getLooper(), UserHandle.ALL, true); + mDevicePresenceMonitor.init(context); } else if (phase == PHASE_BOOT_COMPLETED) { // Run the Association CleanUp job service daily. AssociationCleanUpService.schedule(getContext()); @@ -288,76 +235,84 @@ public class CompanionDeviceManagerService extends SystemService @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) { final AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( userId, packageName, macAddress); - return sanitizeWithCallerChecks(association); + return sanitizeWithCallerChecks(getContext(), association); } @Nullable AssociationInfo getAssociationWithCallerChecks(int associationId) { final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - return sanitizeWithCallerChecks(association); + return sanitizeWithCallerChecks(getContext(), association); } - @Nullable - private AssociationInfo sanitizeWithCallerChecks(@Nullable AssociationInfo association) { - if (association == null) return null; + 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=" + associationId); + + if (!association.shouldBindWhenPresent()) return; final int userId = association.getUserId(); final String packageName = association.getPackageName(); - if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) { - return null; - } - return association; + if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) { + mCompanionAppController.bindCompanionApplication(userId, packageName); + } else if (DEBUG) { + Log.i(TAG, "u" + userId + "\\" + packageName + " is already bound"); + } + mCompanionAppController.notifyCompanionApplicationDeviceAppeared(association); } - // Revoke associations if the selfManaged companion device does not connect for 3 - // months for specific profile. - private void associationCleanUp(String profile) { - for (AssociationInfo ai : mAssociationStore.getAssociations()) { - if (ai.isSelfManaged() - && profile.equals(ai.getDeviceProfile()) - && System.currentTimeMillis() - ai.getLastTimeConnectedMs() - >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) { - Slog.d(LOG_TAG, "Removing the association for associationId: " - + ai.getId() - + " due to the device does not connect for 3 months." - + " Current time: " - + new Date(System.currentTimeMillis())); - disassociateInternal(ai.getId()); - } + 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=" + associationId); + + 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); + } + + // Check if there are other devices associated to the app that are present. + if (shouldBindPackage(userId, packageName)) return; + + mCompanionAppController.unbindCompanionApplication(userId, packageName); } - void maybeGrantAutoRevokeExemptions() { - Slog.d(LOG_TAG, "maybeGrantAutoRevokeExemptions()"); - PackageManager pm = getContext().getPackageManager(); - for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { - SharedPreferences pref = getContext().getSharedPreferences( - new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), - Context.MODE_PRIVATE); - if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { - continue; - } - - try { - final List associations = - mAssociationStore.getAssociationsForUser(userId); - for (AssociationInfo a : associations) { - try { - int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); - exemptFromAutoRevoke(a.getPackageName(), uid); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(LOG_TAG, "Unknown companion package: " + a.getPackageName(), e); - } - } - } finally { - pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); - } - } + private boolean onCompanionApplicationBindingDiedInternal( + @UserIdInt int userId, @NonNull String packageName) { + // TODO(b/218613015): implement. + return false; } - @Override - public void onAssociationChanged( + private void onRebindCompanionApplicationTimeoutInternal( + @UserIdInt int userId, @NonNull String packageName) { + // TODO(b/218613015): implement. + } + + /** + * @return whether the package should be bound (i.e. at least one of the devices associated with + * the package is currently present). + */ + private boolean shouldBindPackage(@UserIdInt int userId, @NonNull String packageName) { + final List packageAssociations = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : packageAssociations) { + if (!association.shouldBindWhenPresent()) continue; + if (mDevicePresenceMonitor.isDevicePresent(association.getId())) return true; + } + return false; + } + + private void onAssociationChangedInternal( @AssociationStore.ChangeType int changeType, AssociationInfo association) { final int id = association.getId(); final int userId = association.getUserId(); @@ -379,8 +334,6 @@ public class CompanionDeviceManagerService extends SystemService notifyListeners(userId, updatedAssociations); } updateAtm(userId, updatedAssociations); - - restartBleScan(); } private void persistStateForUser(@UserIdInt int userId) { @@ -417,15 +370,59 @@ public class CompanionDeviceManagerService extends SystemService } } - class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { + private void onPackageRemoveOrDataClearedInternal( + @UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) { + Log.i(TAG, "onPackageRemove_Or_DataCleared() u" + userId + "/" + + packageName); + } + // Clear associations. + final List associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + mAssociationStore.removeAssociation(association.getId()); + } + + mCompanionAppController.onPackagesChanged(userId); + } + + private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) { + if (DEBUG) Log.i(TAG, "onPackageModified() u" + userId + "/" + packageName); + + final List associationsForPackage = + mAssociationStore.getAssociationsForPackage(userId, packageName); + for (AssociationInfo association : associationsForPackage) { + updateSpecialAccessPermissionForAssociatedPackage(association); + } + + mCompanionAppController.onPackagesChanged(userId); + } + + // Revoke associations if the selfManaged companion device does not connect for 3 + // months for specific profile. + private void associationCleanUp(String profile) { + for (AssociationInfo ai : mAssociationStore.getAssociations()) { + if (ai.isSelfManaged() + && profile.equals(ai.getDeviceProfile()) + && System.currentTimeMillis() - ai.getLastTimeConnectedMs() + >= ASSOCIATION_CLEAN_UP_TIME_WINDOW) { + Slog.i(TAG, "Removing the association for associationId: " + + ai.getId() + + " due to the device does not connect for 3 months."); + disassociateInternal(ai.getId()); + } + } + } + + class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { try { return super.onTransact(code, data, reply, flags); } catch (Throwable e) { - Slog.e(LOG_TAG, "Error during IPC", e); + Slog.e(TAG, "Error during IPC", e); throw ExceptionUtils.propagate(e, RemoteException.class); } } @@ -433,7 +430,7 @@ public class CompanionDeviceManagerService extends SystemService @Override public void associate(AssociationRequest request, IAssociationRequestCallback callback, String packageName, int userId) throws RemoteException { - Slog.i(LOG_TAG, "associate() " + Slog.i(TAG, "associate() " + "request=" + request + ", " + "package=u" + userId + "/" + packageName); enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName, @@ -451,7 +448,7 @@ public class CompanionDeviceManagerService extends SystemService if (!checkCallerCanManageCompanionDevice(getContext())) { // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to // request the feature (also: the caller is the app itself). - checkUsesFeature(packageName, getCallingUserId()); + enforceUsesCompanionDeviceFeature(getContext(), userId, packageName); } return mAssociationStore.getAssociationsForPackage(userId, packageName); @@ -487,6 +484,11 @@ public class CompanionDeviceManagerService extends SystemService @Override public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) { + if (DEBUG) { + Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName + + ", macAddress=" + deviceMacAddress); + } + requireNonNull(deviceMacAddress); requireNonNull(packageName); @@ -503,6 +505,8 @@ public class CompanionDeviceManagerService extends SystemService @Override public void disassociate(int associationId) { + if (DEBUG) Log.i(TAG, "disassociate() associationId=" + associationId); + final AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " @@ -519,9 +523,9 @@ public class CompanionDeviceManagerService extends SystemService throws RemoteException { String callingPackage = component.getPackageName(); checkCanCallNotificationApi(callingPackage); - //TODO: check userId. + // TODO: check userId. String packageTitle = BidiFormatter.getInstance().unicodeWrap( - getPackageInfo(callingPackage, userId) + getPackageInfo(getContext(), userId, callingPackage) .applicationInfo .loadSafeLabel(getContext().getPackageManager(), PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX, @@ -575,26 +579,28 @@ public class CompanionDeviceManagerService extends SystemService @Override public void registerDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId) throws RemoteException { - //TODO: take the userId into account. + // TODO: take the userId into account. registerDevicePresenceListenerActive(callingPackage, deviceAddress, true); } @Override public void unregisterDevicePresenceListenerService(String deviceAddress, String callingPackage, int userId) throws RemoteException { - //TODO: take the userId into account. + // TODO: take the userId into account. registerDevicePresenceListenerActive(callingPackage, deviceAddress, false); } @Override public void dispatchMessage(int messageId, int associationId, byte[] message) throws RemoteException { - //TODO: b/199427116 + // TODO(b/199427116): implement. } @Override public void notifyDeviceAppeared(int associationId) { - final AssociationInfo association = getAssociationWithCallerChecks(associationId); + if (DEBUG) Log.i(TAG, "notifyDevice_Appeared() id=" + associationId); + + AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " + "does not exist " @@ -607,23 +613,20 @@ public class CompanionDeviceManagerService extends SystemService + " is not self-managed. notifyDeviceAppeared(int) can only be called for" + " self-managed associations."); } - - if (!mPresentSelfManagedDevices.add(associationId)) { - Slog.w(LOG_TAG, "Association with ID " + associationId + " is already present"); - return; - } - - AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // timestamp. + association = AssociationInfo.builder(association) .setLastTimeConnected(System.currentTimeMillis()) .build(); - mAssociationStore.updateAssociation(updatedAssociationInfo); + mAssociationStore.updateAssociation(association); - mCompanionDevicePresenceController.onDeviceNotifyAppeared( - updatedAssociationInfo, getContext(), mMainHandler); + mDevicePresenceMonitor.onSelfManagedDeviceConnected(associationId); } @Override public void notifyDeviceDisappeared(int associationId) { + if (DEBUG) Log.i(TAG, "notifyDevice_Disappeared() id=" + associationId); + final AssociationInfo association = getAssociationWithCallerChecks(associationId); if (association == null) { throw new IllegalArgumentException("Association with ID " + associationId + " " @@ -638,14 +641,7 @@ public class CompanionDeviceManagerService extends SystemService + " self-managed associations."); } - if (!mPresentSelfManagedDevices.contains(associationId)) { - Slog.w(LOG_TAG, "Association with ID " + associationId + " is not connected"); - return; - } - - mPresentSelfManagedDevices.remove(associationId); - mCompanionDevicePresenceController.onDeviceNotifyDisappearedAndUnbind( - association, getContext(), mMainHandler); + mDevicePresenceMonitor.onSelfManagedDeviceDisconnected(associationId); } private void registerDevicePresenceListenerActive(String packageName, String deviceAddress, @@ -656,8 +652,7 @@ public class CompanionDeviceManagerService extends SystemService final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, packageName); - final AssociationInfo association = - mAssociationStore.getAssociationsForPackageWithAddress( + AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress( userId, packageName, deviceAddress); if (association == null) { @@ -666,10 +661,14 @@ public class CompanionDeviceManagerService extends SystemService + " for user " + userId)); } - AssociationInfo updatedAssociationInfo = AssociationInfo.builder(association) + // AssociationInfo class is immutable: create a new AssociationInfo object with updated + // flag. + association = AssociationInfo.builder(association) .setNotifyOnDeviceNearby(active) .build(); - mAssociationStore.updateAssociation(updatedAssociationInfo); + mAssociationStore.updateAssociation(association); + + // TODO(b/218615198): correctly handle the case when the device is currently present. } @Override @@ -677,7 +676,7 @@ public class CompanionDeviceManagerService extends SystemService byte[] certificate) { if (!getContext().getPackageManager().hasSigningCertificate( packageName, certificate, CERT_INPUT_SHA256)) { - Slog.e(LOG_TAG, "Given certificate doesn't match the package certificate."); + Slog.e(TAG, "Given certificate doesn't match the package certificate."); return; } @@ -691,10 +690,12 @@ public class CompanionDeviceManagerService extends SystemService final int userId = getCallingUserId(); enforceCallerIsSystemOr(userId, callingPackage); + if (getCallingUid() == SYSTEM_UID) return; + + enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage); checkState(!ArrayUtils.isEmpty( mAssociationStore.getAssociationsForPackage(userId, callingPackage)), "App must have an association before calling this API"); - checkUsesFeature(callingPackage, userId); } @Override @@ -720,47 +721,20 @@ public class CompanionDeviceManagerService extends SystemService } @Override - public void dump(@NonNull FileDescriptor fd, - @NonNull PrintWriter fout, + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter out, @Nullable String[] args) { - if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), LOG_TAG, fout)) { + if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, out)) { return; } - fout.append("Companion Device Associations:").append('\n'); + // TODO(b/218615185): mAssociationStore.dump() instead + out.append("Companion Device Associations:").append('\n'); for (AssociationInfo a : mAssociationStore.getAssociations()) { - fout.append(" ").append(a.toString()).append('\n'); + out.append(" ").append(a.toString()).append('\n'); } - fout.append("Currently Connected Devices:").append('\n'); - for (int i = 0, size = mCurrentlyConnectedDevices.size(); i < size; i++) { - fout.append(" ").append(mCurrentlyConnectedDevices.get(i)).append('\n'); - } - - fout.append("Currently SelfManaged Connected Devices associationId:").append('\n'); - for (Integer associationId : mPresentSelfManagedDevices) { - fout.append(" ").append("AssociationId: ").append( - String.valueOf(associationId)).append('\n'); - } - - fout.append("Devices Last Nearby:").append('\n'); - for (int i = 0, size = mDevicesLastNearby.size(); i < size; i++) { - String device = mDevicesLastNearby.keyAt(i); - Date time = mDevicesLastNearby.valueAt(i); - fout.append(" ").append(device).append(" -> ") - .append(sDateFormat.format(time)).append('\n'); - } - - fout.append("Device Listener Services State:").append('\n'); - for (int i = 0, size = mCompanionDevicePresenceController.mBoundServices.size(); - i < size; i++) { - int userId = mCompanionDevicePresenceController.mBoundServices.keyAt(i); - fout.append(" ") - .append("u").append(Integer.toString(userId)).append(": ") - .append(Objects.toString( - mCompanionDevicePresenceController.mBoundServices.valueAt(i))) - .append('\n'); - } + // TODO(b/218615185): mDevicePresenceMonitor.dump() + // TODO(b/218615185): mCompanionAppController.dump() } } @@ -784,7 +758,7 @@ public class CompanionDeviceManagerService extends SystemService final AssociationInfo association = new AssociationInfo(id, userId, packageName, macAddress, displayName, deviceProfile, selfManaged, false, timestamp, Long.MAX_VALUE); - Slog.i(LOG_TAG, "New CDM association created=" + association); + Slog.i(TAG, "New CDM association created=" + association); mAssociationStore.addAssociation(association); // If the "Device Profile" is specified, make the companion application a holder of the @@ -862,52 +836,50 @@ public class CompanionDeviceManagerService extends SystemService } } - //TODO: also revoke notification access + // TODO: also revoke notification access void disassociateInternal(int associationId) { - onAssociationPreRemove(associationId); - mAssociationStore.removeAssociation(associationId); - } - - void onAssociationPreRemove(int associationId) { final AssociationInfo association = mAssociationStore.getAssociationById(associationId); - if (association.isNotifyOnDeviceNearby() - || (association.isSelfManaged() - && mPresentSelfManagedDevices.contains(association.getId()))) { - mCompanionDevicePresenceController.unbindDevicePresenceListener( - association.getPackageName(), association.getUserId()); - } + final int userId = association.getUserId(); + final String packageName = association.getPackageName(); + final String deviceProfile = association.getDeviceProfile(); - String deviceProfile = association.getDeviceProfile(); + final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId); + + // Removing the association. + mAssociationStore.removeAssociation(associationId); + + final List otherAssociations = + mAssociationStore.getAssociationsForPackage(userId, packageName); + + // Check if the package is associated with other devices with the same profile. + // If not: take away the role. if (deviceProfile != null) { - AssociationInfo otherAssociationWithDeviceProfile = find( - mAssociationStore.getAssociationsForUser(association.getUserId()), - a -> !a.equals(association) && deviceProfile.equals(a.getDeviceProfile())); - if (otherAssociationWithDeviceProfile != null) { - Slog.i(LOG_TAG, "Not revoking " + deviceProfile - + " for " + association - + " - profile still present in " + otherAssociationWithDeviceProfile); - } else { - Binder.withCleanCallingIdentity( - () -> removeRoleHolderForAssociation(getContext(), association)); + final boolean shouldKeepTheRole = any(otherAssociations, + it -> deviceProfile.equals(it.getDeviceProfile())); + if (!shouldKeepTheRole) { + Binder.withCleanCallingIdentity(() -> + removeRoleHolderForAssociation(getContext(), association)); } } + + if (!wasPresent || !association.isNotifyOnDeviceNearby()) return; + // The device was connected and the app was notified: check if we need to unbind the app + // now. + final boolean shouldStayBound = any(otherAssociations, + it -> it.isNotifyOnDeviceNearby() + && mDevicePresenceMonitor.isDevicePresent(it.getId())); + if (shouldStayBound) return; + mCompanionAppController.unbindCompanionApplication(userId, packageName); } private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) { - PackageInfo packageInfo = getPackageInfo( - association.getPackageName(), - association.getUserId()); - if (packageInfo == null) { - return; - } + final PackageInfo packageInfo = + getPackageInfo(getContext(), association.getUserId(), association.getPackageName()); - Binder.withCleanCallingIdentity(obtainRunnable(CompanionDeviceManagerService:: - updateSpecialAccessPermissionAsSystem, this, association, packageInfo) - .recycleOnUse()); + Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo)); } - private void updateSpecialAccessPermissionAsSystem( - AssociationInfo association, PackageInfo packageInfo) { + private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) { if (containsEither(packageInfo.requestedPermissions, android.Manifest.permission.RUN_IN_BACKGROUND, android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) { @@ -916,7 +888,7 @@ public class CompanionDeviceManagerService extends SystemService try { mPowerWhitelistManager.removeFromWhitelist(packageInfo.packageName); } catch (UnsupportedOperationException e) { - Slog.w(LOG_TAG, packageInfo.packageName + " can't be removed from power save" + Slog.w(TAG, packageInfo.packageName + " can't be removed from power save" + " whitelist. It might due to the package is whitelisted by the system."); } } @@ -935,10 +907,6 @@ public class CompanionDeviceManagerService extends SystemService } exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid); - - if (association.isNotifyOnDeviceNearby()) { - restartBleScan(); - } } private void exemptFromAutoRevoke(String packageName, int uid) { @@ -949,23 +917,10 @@ public class CompanionDeviceManagerService extends SystemService packageName, AppOpsManager.MODE_IGNORED); } catch (RemoteException e) { - Slog.w(LOG_TAG, - "Error while granting auto revoke exemption for " + packageName, e); + Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e); } } - private static boolean containsEither(T[] array, T a, T b) { - return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); - } - - @Nullable - private PackageInfo getPackageInfo(String packageName, int userId) { - final int flags = PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS; - return Binder.withCleanCallingIdentity( - () -> getContext().getPackageManager() - .getPackageInfoAsUser(packageName, flags , userId)); - } - private void updateAtm(int userId, List associations) { final Set companionAppUids = new ArraySet<>(); for (AssociationInfo association : associations) { @@ -981,263 +936,86 @@ public class CompanionDeviceManagerService extends SystemService } } - void onDeviceConnected(String address) { - Slog.d(LOG_TAG, "onDeviceConnected(address = " + address + ")"); - mCurrentlyConnectedDevices.add(address); - onDeviceNearby(address); - } + private void maybeGrantAutoRevokeExemptions() { + Slog.d(TAG, "maybeGrantAutoRevokeExemptions()"); - void onDeviceDisconnected(String address) { - Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")"); - - mCurrentlyConnectedDevices.remove(address); - - Date lastSeen = mDevicesLastNearby.get(address); - if (isDeviceDisappeared(lastSeen)) { - onDeviceDisappeared(address); - unscheduleTriggerDeviceDisappearedRunnable(address); - } - } - - private boolean isDeviceDisappeared(Date lastSeen) { - return lastSeen == null || System.currentTimeMillis() - lastSeen.getTime() - >= DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS; - } - - private class BleScanCallback extends ScanCallback { - @Override - public void onScanResult(int callbackType, ScanResult result) { - if (DEBUG) { - Slog.i(LOG_TAG, "onScanResult(callbackType = " - + callbackType + ", result = " + result + ")"); - } - - onDeviceNearby(result.getDevice().getAddress()); - } - - @Override - public void onBatchScanResults(List results) { - for (int i = 0, size = results.size(); i < size; i++) { - onScanResult(CALLBACK_TYPE_ALL_MATCHES, results.get(i)); - } - } - - @Override - public void onScanFailed(int errorCode) { - if (errorCode == SCAN_FAILED_ALREADY_STARTED) { - // ignore - this might happen if BT tries to auto-restore scans for us in the - // future - Slog.i(LOG_TAG, "Ignoring BLE scan error: SCAN_FAILED_ALREADY_STARTED"); - } else { - Slog.w(LOG_TAG, "Failed to start BLE scan: error " + errorCode); - } - } - } - - private class BleStateBroadcastReceiver extends BroadcastReceiver { - - final IntentFilter mIntentFilter = - new IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); - - @Override - public void onReceive(Context context, Intent intent) { - int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1); - int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); - Slog.d(LOG_TAG, "Received BT state transition broadcast: " - + BluetoothAdapter.nameForState(previousState) - + " -> " + BluetoothAdapter.nameForState(newState)); - - boolean bleOn = newState == BluetoothAdapter.STATE_ON - || newState == BluetoothAdapter.STATE_BLE_ON; - if (bleOn) { - if (mBluetoothAdapter.getBluetoothLeScanner() != null) { - startBleScan(); - } else { - Slog.wtf(LOG_TAG, "BLE on, but BluetoothLeScanner == null"); - } - } - } - } - - private class UnbindDeviceListenersRunnable implements Runnable { - - public String getJobId(String address) { - return "CDM_deviceGone_unbind_" + address; - } - - @Override - public void run() { - int size = mDevicesLastNearby.size(); - for (int i = 0; i < size; i++) { - String address = mDevicesLastNearby.keyAt(i); - Date lastNearby = mDevicesLastNearby.valueAt(i); - - if (isDeviceDisappeared(lastNearby)) { - final List associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.unbindDevicePresenceListener( - association.getPackageName(), association.getUserId()); - } - } - } - } - } - } - - private class TriggerDeviceDisappearedRunnable implements Runnable { - - private final String mAddress; - - TriggerDeviceDisappearedRunnable(String address) { - mAddress = address; - } - - public void schedule() { - mMainHandler.removeCallbacks(this); - mMainHandler.postDelayed(this, this, DEVICE_DISAPPEARED_TIMEOUT_MS); - } - - @Override - public void run() { - Slog.d(LOG_TAG, "TriggerDeviceDisappearedRunnable.run(address = " + mAddress + ")"); - if (!mCurrentlyConnectedDevices.contains(mAddress)) { - onDeviceDisappeared(mAddress); - } - } - } - - private void unscheduleTriggerDeviceDisappearedRunnable(String address) { - Runnable r = mTriggerDeviceDisappearedRunnables.get(address); - if (r != null) { - Slog.d(LOG_TAG, - "unscheduling TriggerDeviceDisappearedRunnable(address = " + address + ")"); - mMainHandler.removeCallbacks(r); - } - } - - private void onDeviceNearby(String address) { - Date timestamp = new Date(); - Date oldTimestamp = mDevicesLastNearby.put(address, timestamp); - - cancelUnbindDeviceListener(address); - - mTriggerDeviceDisappearedRunnables - .computeIfAbsent(address, addr -> new TriggerDeviceDisappearedRunnable(address)) - .schedule(); - - // Avoid spamming the app if device is already known to be nearby - boolean justAppeared = oldTimestamp == null - || timestamp.getTime() - oldTimestamp.getTime() >= DEVICE_DISAPPEARED_TIMEOUT_MS; - if (justAppeared) { - Slog.i(LOG_TAG, "onDeviceNearby(justAppeared, address = " + address + ")"); - final List associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.onDeviceNotifyAppeared(association, - getContext(), mMainHandler); - } - } - } - } - - private void onDeviceDisappeared(String address) { - Slog.i(LOG_TAG, "onDeviceDisappeared(address = " + address + ")"); - - boolean hasDeviceListeners = false; - final List associations = - mAssociationStore.getAssociationsByAddress(address); - for (AssociationInfo association : associations) { - if (association.isNotifyOnDeviceNearby()) { - mCompanionDevicePresenceController.onDeviceNotifyDisappeared( - association, getContext(), mMainHandler); - hasDeviceListeners = true; - } - } - - cancelUnbindDeviceListener(address); - if (hasDeviceListeners) { - mMainHandler.postDelayed( - mUnbindDeviceListenersRunnable, - mUnbindDeviceListenersRunnable.getJobId(address), - DEVICE_DISAPPEARED_UNBIND_TIMEOUT_MS); - } - } - - private void cancelUnbindDeviceListener(String address) { - mMainHandler.removeCallbacks( - mUnbindDeviceListenersRunnable, mUnbindDeviceListenersRunnable.getJobId(address)); - } - - private void initBleScanning() { - Slog.i(LOG_TAG, "initBleScanning()"); - - boolean bluetoothReady = mBluetoothAdapter.registerServiceLifecycleCallback( - new BluetoothAdapter.ServiceLifecycleCallback() { - @Override - public void onBluetoothServiceUp() { - Slog.i(LOG_TAG, "Bluetooth stack is up"); - startBleScan(); - } - - @Override - public void onBluetoothServiceDown() { - Slog.w(LOG_TAG, "Bluetooth stack is down"); - } - }); - if (bluetoothReady) { - startBleScan(); - } - } - - void startBleScan() { - Slog.i(LOG_TAG, "startBleScan()"); - - List filters = getBleScanFilters(); - if (filters.isEmpty()) { - return; - } - BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); - if (scanner == null) { - Slog.w(LOG_TAG, "scanner == null (likely BLE isn't ON yet)"); - } else { - scanner.startScan( - filters, - new ScanSettings.Builder().setScanMode(SCAN_MODE_LOW_POWER).build(), - mBleScanCallback); - } - } - - void restartBleScan() { - if (mBluetoothAdapter.getBluetoothLeScanner() != null) { - mBluetoothAdapter.getBluetoothLeScanner().stopScan(mBleScanCallback); - startBleScan(); - } else { - Slog.w(LOG_TAG, "BluetoothLeScanner is null (likely BLE isn't ON yet)."); - } - } - - private List getBleScanFilters() { - ArrayList result = new ArrayList<>(); - ArraySet addressesSeen = new ArraySet<>(); - for (AssociationInfo association : mAssociationStore.getAssociations()) { - if (association.isSelfManaged()) { + PackageManager pm = getContext().getPackageManager(); + for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) { + SharedPreferences pref = getContext().getSharedPreferences( + new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME), + Context.MODE_PRIVATE); + if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) { continue; } - String address = association.getDeviceMacAddressAsString(); - if (addressesSeen.contains(address)) { - continue; - } - if (association.isNotifyOnDeviceNearby()) { - result.add(new ScanFilter.Builder().setDeviceAddress(address).build()); - addressesSeen.add(address); + + try { + final List associations = + mAssociationStore.getAssociationsForUser(userId); + for (AssociationInfo a : associations) { + try { + int uid = pm.getPackageUidAsUser(a.getPackageName(), userId); + exemptFromAutoRevoke(a.getPackageName(), uid); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e); + } + } + } finally { + pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply(); } } - return result; } + private final AssociationStore.OnChangeListener mAssociationStoreChangeListener = + new AssociationStore.OnChangeListener() { + @Override + public void onAssociationChanged(int changeType, AssociationInfo association) { + onAssociationChangedInternal(changeType, association); + } + }; + + private final CompanionDevicePresenceMonitor.Callback mDevicePresenceCallback = + new CompanionDevicePresenceMonitor.Callback() { + @Override + public void onDeviceAppeared(int associationId) { + onDeviceAppearedInternal(associationId); + } + + @Override + public void onDeviceDisappeared(int associationId) { + onDeviceDisappearedInternal(associationId); + } + }; + + private final CompanionApplicationController.Callback mApplicationControllerCallback = + new CompanionApplicationController.Callback() { + @Override + public boolean onCompanionApplicationBindingDied(int userId, @NonNull String packageName) { + return onCompanionApplicationBindingDiedInternal(userId, packageName); + } + + @Override + public void onRebindCompanionApplicationTimeout(int userId, @NonNull String packageName) { + onRebindCompanionApplicationTimeoutInternal(userId, packageName); + } + }; + + private final PackageMonitor mPackageMonitor = new PackageMonitor() { + @Override + public void onPackageRemoved(String packageName, int uid) { + onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); + } + + @Override + public void onPackageDataCleared(String packageName, int uid) { + onPackageRemoveOrDataClearedInternal(getChangingUserId(), packageName); + } + + @Override + public void onPackageModified(String packageName) { + onPackageModifiedInternal(getChangingUserId(), packageName); + } + }; + static int getFirstAssociationIdForUser(@UserIdInt int userId) { // We want the IDs to start from 1, not 0. return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1; @@ -1247,82 +1025,6 @@ public class CompanionDeviceManagerService extends SystemService return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE; } - private class BluetoothDeviceConnectedListener - extends BluetoothAdapter.BluetoothConnectionCallback { - @Override - public void onDeviceConnected(BluetoothDevice device) { - CompanionDeviceManagerService.this.onDeviceConnected(device.getAddress()); - } - - @Override - public void onDeviceDisconnected(BluetoothDevice device, int reason) { - Slog.d(LOG_TAG, device.getAddress() + " disconnected w/ reason: (" + reason + ") " - + BluetoothAdapter.BluetoothConnectionCallback.disconnectReasonText(reason)); - CompanionDeviceManagerService.this.onDeviceDisconnected(device.getAddress()); - } - } - - void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) { - if (getCallingUid() == SYSTEM_UID) return; - - final FeatureInfo[] requestedFeatures = getPackageInfo(pkg, userId).reqFeatures; - if (requestedFeatures != null) { - for (int i = 0; i < requestedFeatures.length; i++) { - if (FEATURE_COMPANION_DEVICE_SETUP.equals(requestedFeatures[i].name)) return; - } - } - - throw new IllegalStateException("Must declare uses-feature " - + FEATURE_COMPANION_DEVICE_SETUP - + " in manifest to use this API"); - } - - private void registerPackageMonitor() { - new PackageMonitor() { - @Override - public void onPackageRemoved(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageRemoved() u" + userId + "/" + packageName); - - clearAssociationForPackage(userId, packageName); - } - - @Override - public void onPackageDataCleared(String packageName, int uid) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageDataCleared() u" + userId + "/" + packageName); - - clearAssociationForPackage(userId, packageName); - } - - @Override - public void onPackageModified(String packageName) { - final int userId = getChangingUserId(); - Slog.i(LOG_TAG, "onPackageModified() u" + userId + "/" + packageName); - - final List associationsForPackage = - mAssociationStore.getAssociationsForPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - updateSpecialAccessPermissionForAssociatedPackage(association); - } - } - }.register(getContext(), FgThread.get().getLooper(), UserHandle.ALL, true); - } - - private void clearAssociationForPackage(@UserIdInt int userId, @NonNull String packageName) { - if (DEBUG) Slog.d(LOG_TAG, "clearAssociationForPackage() u" + userId + "/" + packageName); - - // First, unbind CompanionService if needed. - mCompanionDevicePresenceController.unbindDevicePresenceListener(packageName, userId); - - // Clear associations. - final List associationsForPackage = - mAssociationStore.getAssociationsForPackage(userId, packageName); - for (AssociationInfo association : associationsForPackage) { - mAssociationStore.removeAssociation(association.getId()); - } - } - private static Map> deepUnmodifiableCopy(Map> orig) { final Map> copy = new HashMap<>(); @@ -1334,16 +1036,14 @@ public class CompanionDeviceManagerService extends SystemService return Collections.unmodifiableMap(copy); } - private final class LocalService extends CompanionDeviceManagerServiceInternal { - private final CompanionDeviceManagerService mService; - - LocalService(CompanionDeviceManagerService service) { - mService = service; - } + private static boolean containsEither(T[] array, T a, T b) { + return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); + } + private class LocalService extends CompanionDeviceManagerServiceInternal { @Override public void associationCleanUp(String profile) { - mService.associationCleanUp(profile); + CompanionDeviceManagerService.this.associationCleanUp(profile); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java b/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java deleted file mode 100644 index fc6681705cb6f..0000000000000 --- a/services/companion/java/com/android/server/companion/CompanionDevicePresenceController.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (C) 2021 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.Manifest.permission.BIND_COMPANION_DEVICE_SERVICE; -import static android.content.Context.BIND_IMPORTANT; - -import static com.android.internal.util.CollectionUtils.filter; - -import android.annotation.NonNull; -import android.companion.AssociationInfo; -import android.companion.CompanionDeviceService; -import android.companion.ICompanionDeviceService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Handler; -import android.util.ArrayMap; -import android.util.Slog; - -import com.android.internal.infra.PerUser; -import com.android.internal.infra.ServiceConnector; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * This class creates/removes {@link ServiceConnector}s between {@link CompanionDeviceService} and - * the companion apps. The controller also will notify the companion apps with device status. - */ -public class CompanionDevicePresenceController { - private static final String LOG_TAG = "CompanionDevicePresenceController"; - PerUser>> mBoundServices; - private static final String META_DATA_KEY_PRIMARY = "android.companion.primary"; - private final CompanionDeviceManagerService mService; - - public CompanionDevicePresenceController(CompanionDeviceManagerService service) { - mService = service; - mBoundServices = new PerUser>>() { - @NonNull - @Override - protected ArrayMap> create(int userId) { - return new ArrayMap<>(); - } - }; - } - - void onDeviceNotifyAppeared(AssociationInfo association, Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceAppeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.run( - service -> service.onDeviceAppeared(association)); - } else { - Slog.i(LOG_TAG, "Connecting to " + boundService.mComponentName); - boundService.mServiceConnector.connect(); - } - } - } - - void onDeviceNotifyDisappeared(AssociationInfo association, Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.run(service -> - service.onDeviceDisappeared(association)); - } - } - } - - void onDeviceNotifyDisappearedAndUnbind(AssociationInfo association, - Context context, Handler handler) { - for (BoundService boundService : getDeviceListenerServiceConnector( - association, context, handler)) { - if (boundService.mIsPrimary) { - Slog.i(LOG_TAG, - "Sending onDeviceDisappeared to " + association.getPackageName() + ")"); - boundService.mServiceConnector.post( - service -> { - service.onDeviceDisappeared(association); - }).thenRun(() -> unbindDevicePresenceListener( - association.getPackageName(), association.getUserId())); - } - } - } - - void unbindDevicePresenceListener(String packageName, int userId) { - List boundServices = mBoundServices.forUser(userId) - .remove(packageName); - if (boundServices != null) { - for (BoundService boundService: boundServices) { - Slog.d(LOG_TAG, "Unbinding the serviceConnector: " + boundService.mComponentName); - boundService.mServiceConnector.unbind(); - } - } - } - - private List getDeviceListenerServiceConnector(AssociationInfo a, Context context, - Handler handler) { - return mBoundServices.forUser(a.getUserId()).computeIfAbsent( - a.getPackageName(), - pkg -> createDeviceListenerServiceConnector(a, context, handler)); - } - - private List createDeviceListenerServiceConnector(AssociationInfo a, - Context context, Handler handler) { - List resolveInfos = context - .getPackageManager() - .queryIntentServicesAsUser(new Intent(CompanionDeviceService.SERVICE_INTERFACE), - PackageManager.GET_META_DATA, a.getUserId()); - List packageResolveInfos = filter(resolveInfos, - info -> Objects.equals(info.serviceInfo.packageName, a.getPackageName())); - List serviceConnectors = new ArrayList<>(); - if (!validatePackageInfo(packageResolveInfos, a)) { - return serviceConnectors; - } - for (ResolveInfo packageResolveInfo : packageResolveInfos) { - boolean isPrimary = (packageResolveInfo.serviceInfo.metaData != null - && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) - || packageResolveInfos.size() == 1; - ComponentName componentName = packageResolveInfo.serviceInfo.getComponentName(); - - Slog.i(LOG_TAG, "Initializing CompanionDeviceService binding for " + componentName); - - ServiceConnector serviceConnector = - new ServiceConnector.Impl(context, - new Intent(CompanionDeviceService.SERVICE_INTERFACE).setComponent( - componentName), BIND_IMPORTANT, a.getUserId(), - ICompanionDeviceService.Stub::asInterface) { - @Override - protected long getAutoDisconnectTimeoutMs() { - // Service binding is managed manually based on corresponding device - // being nearby - return -1; - } - - @Override - public void binderDied() { - super.binderDied(); - if (a.isSelfManaged()) { - mBoundServices.forUser(a.getUserId()).remove(a.getPackageName()); - mService.mPresentSelfManagedDevices.remove(a.getId()); - } else { - // Re-connect to the service if process gets killed - handler.postDelayed( - this::connect, - CompanionDeviceManagerService - .DEVICE_LISTENER_DIED_REBIND_TIMEOUT_MS); - } - } - }; - - serviceConnectors.add(new BoundService(componentName, isPrimary, serviceConnector)); - } - return serviceConnectors; - } - - private boolean validatePackageInfo(List packageResolveInfos, - AssociationInfo association) { - if (packageResolveInfos.size() == 0 || packageResolveInfos.size() > 5) { - Slog.e(LOG_TAG, "Device presence listener package must have at least one and not " - + "more than five CompanionDeviceService(s) declared. But " - + association.getPackageName() - + " has " + packageResolveInfos.size()); - return false; - } - - int primaryCount = 0; - for (ResolveInfo packageResolveInfo : packageResolveInfos) { - String servicePermission = packageResolveInfo.serviceInfo.permission; - if (!BIND_COMPANION_DEVICE_SERVICE.equals(servicePermission)) { - Slog.e(LOG_TAG, "Binding CompanionDeviceService must have " - + BIND_COMPANION_DEVICE_SERVICE + " permission."); - return false; - } - - if (packageResolveInfo.serviceInfo.metaData != null - && packageResolveInfo.serviceInfo.metaData.getBoolean(META_DATA_KEY_PRIMARY)) { - primaryCount++; - if (primaryCount > 1) { - Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService " - + "to be bound but " - + association.getPackageName() + "has " + primaryCount); - return false; - } - } - } - - if (packageResolveInfos.size() > 1 && primaryCount == 0) { - Slog.e(LOG_TAG, "Must have exactly one primary CompanionDeviceService " - + "to be bound when declare more than one CompanionDeviceService but " - + association.getPackageName() + " has " + primaryCount); - return false; - } - - if (packageResolveInfos.size() == 1 && primaryCount != 0) { - Slog.w(LOG_TAG, "Do not need the primary metadata if there's only one" - + " CompanionDeviceService " + "but " + association.getPackageName() - + " has " + primaryCount); - } - - return true; - } - - private static class BoundService { - private final ComponentName mComponentName; - private final boolean mIsPrimary; - private final ServiceConnector mServiceConnector; - - BoundService(ComponentName componentName, - boolean isPrimary, ServiceConnector serviceConnector) { - this.mComponentName = componentName; - this.mIsPrimary = isPrimary; - this.mServiceConnector = serviceConnector; - } - } -} diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java index fcb14a4f04d0f..818f0cf8dd420 100644 --- a/services/companion/java/com/android/server/companion/PackageUtils.java +++ b/services/companion/java/com/android/server/companion/PackageUtils.java @@ -21,7 +21,7 @@ import static android.content.pm.PackageManager.GET_CONFIGURATIONS; import static android.content.pm.PackageManager.GET_META_DATA; import static android.content.pm.PackageManager.GET_PERMISSIONS; -import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; import android.Manifest; import android.annotation.NonNull; @@ -96,7 +96,7 @@ final class PackageUtils { final boolean requiresPermission = Manifest.permission.BIND_COMPANION_DEVICE_SERVICE .equals(resolveInfo.serviceInfo.permission); if (!requiresPermission) { - Slog.w(LOG_TAG, "CompanionDeviceService " + Slog.w(TAG, "CompanionDeviceService " + service.getComponentName().flattenToShortString() + " must require " + "android.permission.BIND_COMPANION_DEVICE_SERVICE"); continue; diff --git a/services/companion/java/com/android/server/companion/PermissionsUtils.java b/services/companion/java/com/android/server/companion/PermissionsUtils.java index 0e593e14a0378..ac1bf1bd8c23b 100644 --- a/services/companion/java/com/android/server/companion/PermissionsUtils.java +++ b/services/companion/java/com/android/server/companion/PermissionsUtils.java @@ -36,6 +36,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; @@ -190,6 +191,19 @@ final class PermissionsUtils { return checkCallerCanManageCompanionDevice(context); } + static @Nullable 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; diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java index 904283f4e60ea..35488a80b78b9 100644 --- a/services/companion/java/com/android/server/companion/RolesUtils.java +++ b/services/companion/java/com/android/server/companion/RolesUtils.java @@ -19,6 +19,7 @@ package com.android.server.companion; import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP; import static com.android.server.companion.CompanionDeviceManagerService.DEBUG; +import static com.android.server.companion.CompanionDeviceManagerService.TAG; import android.annotation.NonNull; import android.annotation.SuppressLint; @@ -35,7 +36,6 @@ import java.util.List; /** Utility methods for accessing {@link RoleManager} APIs. */ @SuppressLint("LongLogTag") final class RolesUtils { - private static final String TAG = CompanionDeviceManagerService.LOG_TAG; static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId, @NonNull String packageName, @NonNull String role) {