diff options
| author | 2020-02-10 23:17:28 -0800 | |
|---|---|---|
| committer | 2020-02-19 14:48:43 -0800 | |
| commit | 9a3501d6bb1e12d6becda8837613e45e03a2cb4b (patch) | |
| tree | 062845cc3ff59f7992089945a6d7e6be78eddf36 | |
| parent | b4358fdfd5777325afc6c7987bf00d6e705a33f5 (diff) | |
Share Standby Bucket state between cross profile interacting apps
Cross profile connected apps can provide the functionality of multiple
app from just one of the apps. If the user uses only one of the apps to
interact with all of the connected apps, the connected apps will fall
into low standby buckets. Cross profile connecting apps and using the
conncted apps is the clear signal of usage for both apps, so this
change treats all usage of one app to be equal to the other.
Fixes: 140808123
Test: atest com.android.server.usage.AppStandbyControllerTests
Change-Id: I9e3bd71c7b486417b3943778edece4ab29d4c41d
3 files changed, 135 insertions, 17 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index e343478ec61f..ad50d57a44b9 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -65,6 +65,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; +import android.content.pm.CrossProfileAppsInternal; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -283,6 +284,12 @@ public class AppStandbyController implements AppStandbyInternal { * start is the first usage of the app */ long mInitialForegroundServiceStartTimeoutMillis; + /** + * User usage that would elevate an app's standby bucket will also elevate the standby bucket of + * cross profile connected apps. Explicit standby bucket setting via + * {@link #setAppStandbyBucket(String, int, int, int, int)} will not be propagated. + */ + boolean mLinkCrossProfileApps; private volatile boolean mAppIdleEnabled; private boolean mIsCharging; @@ -445,10 +452,12 @@ public class AppStandbyController implements AppStandbyInternal { continue; } if (!packageName.equals(providerPkgName)) { + final List<UserHandle> linkedProfiles = getCrossProfileTargets(packageName, + userId); synchronized (mAppIdleLock) { - reportNoninteractiveUsageLocked(packageName, userId, STANDBY_BUCKET_ACTIVE, - REASON_SUB_USAGE_SYNC_ADAPTER, elapsedRealtime, - mSyncAdapterTimeoutMillis); + reportNoninteractiveUsageCrossUserLocked(packageName, userId, + STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_SYNC_ADAPTER, + elapsedRealtime, mSyncAdapterTimeoutMillis, linkedProfiles); } } } catch (PackageManager.NameNotFoundException e) { @@ -477,10 +486,10 @@ public class AppStandbyController implements AppStandbyInternal { } final long elapsedRealtime = mInjector.elapsedRealtime(); - + final List<UserHandle> linkedProfiles = getCrossProfileTargets(packageName, userId); synchronized (mAppIdleLock) { - reportNoninteractiveUsageLocked(packageName, userId, bucketToPromote, - usageReason, elapsedRealtime, durationMillis); + reportNoninteractiveUsageCrossUserLocked(packageName, userId, bucketToPromote, + usageReason, elapsedRealtime, durationMillis, linkedProfiles); } } @@ -492,10 +501,11 @@ public class AppStandbyController implements AppStandbyInternal { final int currentBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId, elapsedRealtime); if (currentBucket == STANDBY_BUCKET_NEVER) { + final List<UserHandle> linkedProfiles = getCrossProfileTargets(packageName, userId); // Bring the app out of the never bucket - reportNoninteractiveUsageLocked(packageName, userId, STANDBY_BUCKET_WORKING_SET, - REASON_SUB_USAGE_UNEXEMPTED_SYNC_SCHEDULED, elapsedRealtime, - mUnexemptedSyncScheduledTimeoutMillis); + reportNoninteractiveUsageCrossUserLocked(packageName, userId, + STANDBY_BUCKET_WORKING_SET, REASON_SUB_USAGE_UNEXEMPTED_SYNC_SCHEDULED, + elapsedRealtime, mUnexemptedSyncScheduledTimeoutMillis, linkedProfiles); } } } @@ -504,14 +514,39 @@ public class AppStandbyController implements AppStandbyInternal { if (!mAppIdleEnabled) return; final long elapsedRealtime = mInjector.elapsedRealtime(); - + final List<UserHandle> linkedProfiles = getCrossProfileTargets(packageName, userId); synchronized (mAppIdleLock) { - reportNoninteractiveUsageLocked(packageName, userId, STANDBY_BUCKET_ACTIVE, + reportNoninteractiveUsageCrossUserLocked(packageName, userId, STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_EXEMPTED_SYNC_START, elapsedRealtime, - mExemptedSyncStartTimeoutMillis); + mExemptedSyncStartTimeoutMillis, linkedProfiles); + } + } + + /** + * Helper method to report indirect user usage of an app and handle reporting the usage + * against cross profile connected apps. <br> + * Use {@link #reportNoninteractiveUsageLocked(String, int, int, int, long, long)} if + * cross profile connected apps do not need to be handled. + */ + private void reportNoninteractiveUsageCrossUserLocked(String packageName, int userId, + int bucket, int subReason, long elapsedRealtime, long nextCheckDelay, + List<UserHandle> otherProfiles) { + reportNoninteractiveUsageLocked(packageName, userId, bucket, subReason, elapsedRealtime, + nextCheckDelay); + final int size = otherProfiles.size(); + for (int profileIndex = 0; profileIndex < size; profileIndex++) { + final int otherUserId = otherProfiles.get(profileIndex).getIdentifier(); + reportNoninteractiveUsageLocked(packageName, otherUserId, bucket, subReason, + elapsedRealtime, nextCheckDelay); } } + /** + * Helper method to report indirect user usage of an app. <br> + * Use + * {@link #reportNoninteractiveUsageCrossUserLocked(String, int, int, int, long, long, List)} + * if cross profile connected apps need to be handled. + */ private void reportNoninteractiveUsageLocked(String packageName, int userId, int bucket, int subReason, long elapsedRealtime, long nextCheckDelay) { final AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId, bucket, @@ -766,8 +801,16 @@ public class AppStandbyController implements AppStandbyInternal { || eventType == UsageEvents.Event.SLICE_PINNED || eventType == UsageEvents.Event.SLICE_PINNED_PRIV || eventType == UsageEvents.Event.FOREGROUND_SERVICE_START)) { + final String pkg = event.getPackageName(); + final List<UserHandle> linkedProfiles = getCrossProfileTargets(pkg, userId); synchronized (mAppIdleLock) { - reportEventLocked(event.getPackageName(), eventType, elapsedRealtime, userId); + reportEventLocked(pkg, eventType, elapsedRealtime, userId); + + final int size = linkedProfiles.size(); + for (int profileIndex = 0; profileIndex < size; profileIndex++) { + final int linkedUserId = linkedProfiles.get(profileIndex).getIdentifier(); + reportEventLocked(pkg, eventType, elapsedRealtime, linkedUserId); + } } } } @@ -826,6 +869,16 @@ public class AppStandbyController implements AppStandbyInternal { } } + /** + * Note: don't call this with the lock held since it makes calls to other system services. + */ + private @NonNull List<UserHandle> getCrossProfileTargets(String pkg, int userId) { + synchronized (mAppIdleLock) { + if (!mLinkCrossProfileApps) return Collections.emptyList(); + } + return mInjector.getValidCrossProfileTargets(pkg, userId); + } + private int usageEventToSubReason(int eventType) { switch (eventType) { case UsageEvents.Event.ACTIVITY_RESUMED: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND; @@ -1589,6 +1642,7 @@ public class AppStandbyController implements AppStandbyInternal { private PackageManagerInternal mPackageManagerInternal; private DisplayManager mDisplayManager; private PowerManager mPowerManager; + private CrossProfileAppsInternal mCrossProfileAppsInternal; int mBootPhase; /** * The minimum amount of time required since the last user interaction before an app can be @@ -1620,6 +1674,8 @@ public class AppStandbyController implements AppStandbyInternal { Context.DISPLAY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); mBatteryManager = mContext.getSystemService(BatteryManager.class); + mCrossProfileAppsInternal = LocalServices.getService( + CrossProfileAppsInternal.class); final ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -1727,6 +1783,17 @@ public class AppStandbyController implements AppStandbyInternal { public boolean isDeviceIdleMode() { return mPowerManager.isDeviceIdleMode(); } + + public List<UserHandle> getValidCrossProfileTargets(String pkg, int userId) { + final int uid = mPackageManagerInternal.getPackageUidInternal(pkg, 0, userId); + if (uid < 0 + || !mPackageManagerInternal.getPackage(uid).isCrossProfile() + || !mCrossProfileAppsInternal + .verifyUidHasInteractAcrossProfilePermission(pkg, uid)) { + return Collections.emptyList(); + } + return mCrossProfileAppsInternal.getTargetUserProfiles(pkg, userId); + } } class AppStandbyHandler extends Handler { @@ -1857,6 +1924,8 @@ public class AppStandbyController implements AppStandbyInternal { "initial_foreground_service_start_duration"; private static final String KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS = "auto_restricted_bucket_delay_ms"; + private static final String KEY_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = + "cross_profile_apps_share_standby_buckets"; public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR; public static final long DEFAULT_NOTIFICATION_TIMEOUT = 12 * ONE_HOUR; public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = 2 * ONE_HOUR; @@ -1868,6 +1937,7 @@ public class AppStandbyController implements AppStandbyInternal { public static final long DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT = 10 * ONE_MINUTE; public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT = 30 * ONE_MINUTE; public static final long DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS = ONE_DAY; + public static final boolean DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS = true; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -1973,6 +2043,10 @@ public class AppStandbyController implements AppStandbyInternal { mParser.getDurationMillis(KEY_AUTO_RESTRICTED_BUCKET_DELAY_MS, COMPRESS_TIME ? ONE_MINUTE : DEFAULT_AUTO_RESTRICTED_BUCKET_DELAY_MS)); + + mLinkCrossProfileApps = mParser.getBoolean( + KEY_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS, + DEFAULT_CROSS_PROFILE_APPS_SHARE_STANDBY_BUCKETS); } // Check if app_idle_enabled has changed. Do this after getting the rest of the settings diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 0fa261a6a891..2c73874eef76 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11491,6 +11491,7 @@ public final class Settings { * exempted_sync_duration (long) * system_interaction_duration (long) * initial_foreground_service_start_duration (long) + * cross_profile_apps_share_standby_buckets (boolean) * </pre> * * <p> diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 9e577636c1b3..0fdffd554b36 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -66,6 +66,7 @@ import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.ArraySet; import android.view.Display; @@ -83,6 +84,7 @@ import org.junit.runner.RunWith; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -101,6 +103,8 @@ public class AppStandbyControllerTests { private static final int UID_EXEMPTED_1 = 10001; private static final int USER_ID = 0; private static final int USER_ID2 = 10; + private static final UserHandle USER_HANDLE_USER2 = new UserHandle(USER_ID2); + private static final int USER_ID3 = 11; private static final String PACKAGE_UNKNOWN = "com.example.unknown"; @@ -150,6 +154,8 @@ public class AppStandbyControllerTests { boolean mDisplayOn; DisplayManager.DisplayListener mDisplayListener; String mBoundWidgetPackage = PACKAGE_EXEMPTED_1; + int[] mRunningUsers = new int[] {USER_ID}; + List<UserHandle> mCrossProfileTargets = Collections.emptyList(); MyInjector(Context context, Looper looper) { super(context, looper); @@ -212,7 +218,7 @@ public class AppStandbyControllerTests { @Override int[] getRunningUserIds() { - return new int[] {USER_ID}; + return mRunningUsers; } @Override @@ -248,6 +254,11 @@ public class AppStandbyControllerTests { return false; } + @Override + public List<UserHandle> getValidCrossProfileTargets(String pkg, int userId) { + return mCrossProfileTargets; + } + // Internal methods void setDisplayOn(boolean on) { @@ -379,10 +390,15 @@ public class AppStandbyControllerTests { } private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) { + assertTimeout(controller, elapsedTime, bucket, USER_ID); + } + + private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket, + int userId) { mInjector.mElapsedRealtime = elapsedTime; - controller.checkIdleStates(USER_ID); + controller.checkIdleStates(userId); assertEquals(bucket, - controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime, + controller.getAppStandbyBucket(PACKAGE_1, userId, mInjector.mElapsedRealtime, false)); } @@ -397,7 +413,11 @@ public class AppStandbyControllerTests { } private int getStandbyBucket(AppStandbyController controller, String packageName) { - return controller.getAppStandbyBucket(packageName, USER_ID, mInjector.mElapsedRealtime, + return getStandbyBucket(USER_ID, controller, packageName); + } + + private int getStandbyBucket(int userId, AppStandbyController controller, String packageName) { + return controller.getAppStandbyBucket(packageName, userId, mInjector.mElapsedRealtime, true); } @@ -1012,6 +1032,29 @@ public class AppStandbyControllerTests { assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID); } + @Test + public void testUserInteraction_CrossProfile() throws Exception { + mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3}; + mInjector.mCrossProfileTargets = Arrays.asList(USER_HANDLE_USER2); + reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + assertEquals("Cross profile connected package bucket should be elevated on usage", + STANDBY_BUCKET_ACTIVE, getStandbyBucket(USER_ID2, mController, PACKAGE_1)); + assertEquals("Not Cross profile connected package bucket should not be elevated on usage", + STANDBY_BUCKET_NEVER, getStandbyBucket(USER_ID3, mController, PACKAGE_1)); + + assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID); + assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE, USER_ID2); + + assertTimeout(mController, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET, USER_ID); + assertTimeout(mController, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET, USER_ID2); + + mInjector.mCrossProfileTargets = Collections.emptyList(); + reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1); + assertEquals("No longer cross profile connected package bucket should not be " + + "elevated on usage", + STANDBY_BUCKET_WORKING_SET, getStandbyBucket(USER_ID2, mController, PACKAGE_1)); + } + private String getAdminAppsStr(int userId) { return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId)); } |