Merge "Share Standby Bucket state between cross profile interacting apps"
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -11509,6 +11509,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>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user