diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index f20b824e6bf75..8db5f9a1f51be 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7044,7 +7044,7 @@ android:permission="android.permission.BIND_JOB_SERVICE"> - diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 8622ef0940d54..0bfe2825bdd53 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -78,6 +78,7 @@ import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; @@ -121,8 +122,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_CLEAN_UP_TIME_WINDOW = DAYS.toMillis(3 * 30); // 3 months + private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90); private PersistentDataStore mPersistentStore; private final PersistUserStateHandler mUserPersistenceHandler; @@ -211,8 +214,8 @@ public class CompanionDeviceManagerService extends SystemService { 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()); + // Run the Inactive Association Removal job service daily. + InactiveAssociationsRemovalService.schedule(getContext()); } } @@ -410,17 +413,20 @@ public class CompanionDeviceManagerService extends SystemService { mCompanionAppController.onPackagesChanged(userId); } - // Revoke associations if the selfManaged companion device does not connect for 3 - // months for specific profile. - private void associationCleanUp(String profile) { + // Revoke associations if the selfManaged companion device does not connect for 3 months. + void removeInactiveSelfManagedAssociations() { + final long currentTime = System.currentTimeMillis(); + long removalWindow = SystemProperties.getLong(SYS_PROP_DEBUG_REMOVAL_TIME_WINDOW, -1); + if (removalWindow <= 0) { + // 0 or negative values indicate that the sysprop was never set or should be ignored. + removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT; + } + for (AssociationInfo 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."); + if (!ai.isSelfManaged()) continue; + final boolean isInactive = currentTime - ai.getLastTimeConnectedMs() >= removalWindow; + if (isInactive) { + Slog.i(TAG, "Removing inactive self-managed association: " + ai.getId()); disassociateInternal(ai.getId()); } } @@ -1078,10 +1084,10 @@ public class CompanionDeviceManagerService extends SystemService { return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b); } - private class LocalService extends CompanionDeviceManagerServiceInternal { + private class LocalService implements CompanionDeviceManagerServiceInternal { @Override - public void associationCleanUp(String profile) { - CompanionDeviceManagerService.this.associationCleanUp(profile); + public void removeInactiveSelfManagedAssociations() { + CompanionDeviceManagerService.this.removeInactiveSelfManagedAssociations(); } } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java index 326fefe1f8eaf..36492407d4639 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java @@ -18,12 +18,10 @@ package com.android.server.companion; /** * Companion Device Manager Local System Service Interface. - * - * @hide Only for use within the system server. */ -public abstract class CompanionDeviceManagerServiceInternal { +interface CompanionDeviceManagerServiceInternal { /** - * @see CompanionDeviceManagerService#associationCleanUp + * @see CompanionDeviceManagerService#removeInactiveSelfManagedAssociations */ - public abstract void associationCleanUp(String profile); + void removeInactiveSelfManagedAssociations(); } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 6a19a42723c5c..434d283b3f8ae 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -17,6 +17,7 @@ package com.android.server.companion; import android.companion.AssociationInfo; +import android.os.Binder; import android.os.ShellCommand; import android.util.Log; import android.util.Slog; @@ -96,6 +97,16 @@ class CompanionDeviceShellCommand extends ShellCommand { mDevicePresenceMonitor.simulateDeviceDisappeared(associationId); break; + case "remove-inactive-associations": { + // This command should trigger the same "clean-up" job as performed by the + // InactiveAssociationsRemovalService JobService. However, since the + // InactiveAssociationsRemovalService run as system, we want to run this + // as system (not as shell/root) as well. + Binder.withCleanCallingIdentity( + mService::removeInactiveSelfManagedAssociations); + } + break; + default: return handleDefaultCommands(cmd); } @@ -142,6 +153,12 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than"); pw.println(" 60 seconds ago."); pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + + pw.println(" remove-inactive-associations"); + pw.println(" Remove self-managed associations that have not been active "); + pw.println(" for a long time (90 days or as configured via "); + pw.println(" \"debug.cdm.cdmservice.cleanup_time_window\" system property). "); + pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); } private int getNextIntArgRequired() { diff --git a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java similarity index 68% rename from services/companion/java/com/android/server/companion/AssociationCleanUpService.java rename to services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java index 55246e14d5920..3482863032f94 100644 --- a/services/companion/java/com/android/server/companion/AssociationCleanUpService.java +++ b/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -24,10 +24,8 @@ import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; -import android.companion.AssociationRequest; import android.content.ComponentName; import android.content.Context; -import android.os.AsyncTask; import android.util.Slog; import com.android.server.LocalServices; @@ -37,26 +35,24 @@ import com.android.server.LocalServices; * 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. */ -public class AssociationCleanUpService extends JobService { - private static final int JOB_ID = AssociationCleanUpService.class.hashCode(); +public class InactiveAssociationsRemovalService extends JobService { + private static final int JOB_ID = InactiveAssociationsRemovalService.class.hashCode(); private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1); @Override public boolean onStartJob(final JobParameters params) { - 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(() -> { - LocalServices.getService(CompanionDeviceManagerServiceInternal.class) - .associationCleanUp(AssociationRequest.DEVICE_PROFILE_APP_STREAMING); - jobFinished(params, false); - }); + Slog.i(TAG, "Execute the Association Removal job"); + // Special policy for selfManaged that need to revoke associations if the device + // does not connect for 90 days. + LocalServices.getService(CompanionDeviceManagerServiceInternal.class) + .removeInactiveSelfManagedAssociations(); + jobFinished(params, false); return true; } @Override public boolean onStopJob(final JobParameters params) { - Slog.i(TAG, "Association cleanup job stopped; id=" + params.getJobId() + Slog.i(TAG, "Association removal job stopped; id=" + params.getJobId() + ", reason=" + JobParameters.getInternalReasonCodeDescription( params.getInternalStopReasonCode())); @@ -64,10 +60,10 @@ public class AssociationCleanUpService extends JobService { } static void schedule(Context context) { - Slog.i(TAG, "Scheduling the Association Cleanup job"); + Slog.i(TAG, "Scheduling the Association Removal job"); final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); final JobInfo job = new JobInfo.Builder(JOB_ID, - new ComponentName(context, AssociationCleanUpService.class)) + new ComponentName(context, InactiveAssociationsRemovalService.class)) .setRequiresCharging(true) .setRequiresDeviceIdle(true) .setPeriodic(ONE_DAY_INTERVAL) @@ -75,3 +71,4 @@ public class AssociationCleanUpService extends JobService { jobScheduler.schedule(job); } } +