summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java100
-rw-r--r--core/java/android/provider/Settings.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java51
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));
}