Loading core/java/android/app/usage/AppStandby.java 0 → 100644 +83 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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 android.app.usage; import android.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets * that affect how frequently they can run in the background or perform other battery-consuming * actions. Buckets will be assigned based on how frequently or when the system thinks the user * is likely to use the app. * @hide */ public class AppStandby { /** The app was used very recently, currently in use or likely to be used very soon. */ public static final int STANDBY_BUCKET_ACTIVE = 0; // Leave some gap in case we want to increase the number of buckets /** The app was used recently and/or likely to be used in the next few hours */ public static final int STANDBY_BUCKET_WORKING_SET = 3; // Leave some gap in case we want to increase the number of buckets /** The app was used in the last few days and/or likely to be used in the next few days */ public static final int STANDBY_BUCKET_FREQUENT = 6; // Leave some gap in case we want to increase the number of buckets /** The app has not be used for several days and/or is unlikely to be used for several days */ public static final int STANDBY_BUCKET_RARE = 9; // Leave some gap in case we want to increase the number of buckets /** The app has never been used. */ public static final int STANDBY_BUCKET_NEVER = 12; /** Reason for bucketing -- default initial state */ public static final String REASON_DEFAULT = "default"; /** Reason for bucketing -- timeout */ public static final String REASON_TIMEOUT = "timeout"; /** Reason for bucketing -- usage */ public static final String REASON_USAGE = "usage"; /** Reason for bucketing -- forced by user / shell command */ public static final String REASON_FORCED = "forced"; /** * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will * be appended. */ public static final String REASON_PREDICTED = "predicted"; @IntDef(flag = false, value = { STANDBY_BUCKET_ACTIVE, STANDBY_BUCKET_WORKING_SET, STANDBY_BUCKET_FREQUENT, STANDBY_BUCKET_RARE, STANDBY_BUCKET_NEVER, }) @Retention(RetentionPolicy.SOURCE) public @interface StandbyBuckets {} } core/java/android/app/usage/IUsageStatsManager.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -36,4 +36,6 @@ interface IUsageStatsManager { void onCarrierPrivilegedAppsChanged(); void reportChooserSelection(String packageName, int userId, String contentType, in String[] annotations, String action); int getAppStandbyBucket(String packageName, String callingPackage, int userId); void setAppStandbyBucket(String packageName, int bucket, int userId); } core/java/android/app/usage/UsageStatsManager.java +24 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.app.usage; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.usage.AppStandby.StandbyBuckets; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; Loading Loading @@ -246,6 +247,29 @@ public final class UsageStatsManager { } } /** * @hide */ public @StandbyBuckets int getAppStandbyBucket(String packageName) { try { return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { } return AppStandby.STANDBY_BUCKET_ACTIVE; } /** * @hide */ public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) { try { mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId()); } catch (RemoteException e) { // Nothing to do } } /** * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app Loading services/core/java/com/android/server/am/ActivityManagerShellCommand.java +25 −0 Original line number Diff line number Diff line Loading @@ -222,6 +222,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSetInactive(pw); case "get-inactive": return runGetInactive(pw); case "set-standby-bucket": return runSetStandbyBucket(pw); case "send-trim-memory": return runSendTrimMemory(pw); case "display": Loading Loading @@ -1824,6 +1826,27 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } int runSetStandbyBucket(PrintWriter pw) throws RemoteException { int userId = UserHandle.USER_CURRENT; String opt; while ((opt=getNextOption()) != null) { if (opt.equals("--user")) { userId = UserHandle.parseUserArg(getNextArgRequired()); } else { getErrPrintWriter().println("Error: Unknown option: " + opt); return -1; } } String packageName = getNextArgRequired(); String value = getNextArgRequired(); IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( Context.USAGE_STATS_SERVICE)); usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId); return 0; } int runGetInactive(PrintWriter pw) throws RemoteException { int userId = UserHandle.USER_CURRENT; Loading Loading @@ -2571,6 +2594,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Sets the inactive state of an app."); pw.println(" get-inactive [--user <USER_ID>] <PACKAGE>"); pw.println(" Returns the inactive state of an app."); pw.println(" set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>"); pw.println(" Puts an app in the standby bucket."); pw.println(" send-trim-memory [--user <USER_ID>] <PROCESS>"); pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]"); pw.println(" Send a memory trim event to a <PROCESS>. May also supply a raw trim int level."); Loading services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +36 −21 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package com.android.server.usage; import static android.app.usage.AppStandby.REASON_TIMEOUT; import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE; import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE; import android.app.usage.AppStandby; import android.os.FileUtils; import android.test.AndroidTestCase; Loading @@ -28,6 +33,8 @@ public class AppIdleHistoryTests extends AndroidTestCase { final static String PACKAGE_1 = "com.android.testpackage1"; final static String PACKAGE_2 = "com.android.testpackage2"; final static int USER_ID = 0; @Override protected void setUp() throws Exception { super.setUp(); Loading @@ -42,7 +49,6 @@ public class AppIdleHistoryTests extends AndroidTestCase { } public void testFilesCreation() { final int userId = 0; AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0); aih.updateDisplay(true, /* elapsedRealtime= */ 1000); Loading @@ -50,9 +56,9 @@ public class AppIdleHistoryTests extends AndroidTestCase { // Screen On time file should be written right away assertTrue(aih.getScreenOnTimeFile().exists()); aih.writeAppIdleTimes(userId); aih.writeAppIdleTimes(USER_ID); // stats file should be written now assertTrue(new File(new File(mStorageDir, "users/" + userId), assertTrue(new File(new File(mStorageDir, "users/" + USER_ID), AppIdleHistory.APP_IDLE_FILENAME).exists()); } Loading @@ -77,24 +83,33 @@ public class AppIdleHistoryTests extends AndroidTestCase { assertEquals(aih2.getScreenOnTime(13000), 4000); } public void testPackageEvents() { public void testBuckets() { AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000); aih.setThresholds(4000, 1000); aih.updateDisplay(true, 1000); // App is not-idle by default assertFalse(aih.isIdle(PACKAGE_1, 0, 1500)); // Still not idle assertFalse(aih.isIdle(PACKAGE_1, 0, 3000)); // Idle now assertTrue(aih.isIdle(PACKAGE_1, 0, 8000)); // Not idle assertFalse(aih.isIdle(PACKAGE_2, 0, 9000)); // Screen off aih.updateDisplay(false, 9100); // Still idle after 10 seconds because screen hasn't been on long enough assertFalse(aih.isIdle(PACKAGE_2, 0, 20000)); aih.updateDisplay(true, 21000); assertTrue(aih.isIdle(PACKAGE_2, 0, 23000)); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE, AppStandby.REASON_USAGE); // ACTIVE means not idle assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000)); aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE, AppStandby.REASON_USAGE); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE, REASON_TIMEOUT); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE); assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT); // RARE is considered idle assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000)); assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000)); // Check persistence aih.writeAppIdleDurations(); aih.writeAppIdleTimes(USER_ID); aih = new AppIdleHistory(mStorageDir, 4000); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE); assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT); } } No newline at end of file Loading
core/java/android/app/usage/AppStandby.java 0 → 100644 +83 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 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 android.app.usage; import android.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Set of constants for app standby buckets and reasons. Apps will be moved into different buckets * that affect how frequently they can run in the background or perform other battery-consuming * actions. Buckets will be assigned based on how frequently or when the system thinks the user * is likely to use the app. * @hide */ public class AppStandby { /** The app was used very recently, currently in use or likely to be used very soon. */ public static final int STANDBY_BUCKET_ACTIVE = 0; // Leave some gap in case we want to increase the number of buckets /** The app was used recently and/or likely to be used in the next few hours */ public static final int STANDBY_BUCKET_WORKING_SET = 3; // Leave some gap in case we want to increase the number of buckets /** The app was used in the last few days and/or likely to be used in the next few days */ public static final int STANDBY_BUCKET_FREQUENT = 6; // Leave some gap in case we want to increase the number of buckets /** The app has not be used for several days and/or is unlikely to be used for several days */ public static final int STANDBY_BUCKET_RARE = 9; // Leave some gap in case we want to increase the number of buckets /** The app has never been used. */ public static final int STANDBY_BUCKET_NEVER = 12; /** Reason for bucketing -- default initial state */ public static final String REASON_DEFAULT = "default"; /** Reason for bucketing -- timeout */ public static final String REASON_TIMEOUT = "timeout"; /** Reason for bucketing -- usage */ public static final String REASON_USAGE = "usage"; /** Reason for bucketing -- forced by user / shell command */ public static final String REASON_FORCED = "forced"; /** * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will * be appended. */ public static final String REASON_PREDICTED = "predicted"; @IntDef(flag = false, value = { STANDBY_BUCKET_ACTIVE, STANDBY_BUCKET_WORKING_SET, STANDBY_BUCKET_FREQUENT, STANDBY_BUCKET_RARE, STANDBY_BUCKET_NEVER, }) @Retention(RetentionPolicy.SOURCE) public @interface StandbyBuckets {} }
core/java/android/app/usage/IUsageStatsManager.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -36,4 +36,6 @@ interface IUsageStatsManager { void onCarrierPrivilegedAppsChanged(); void reportChooserSelection(String packageName, int userId, String contentType, in String[] annotations, String action); int getAppStandbyBucket(String packageName, String callingPackage, int userId); void setAppStandbyBucket(String packageName, int bucket, int userId); }
core/java/android/app/usage/UsageStatsManager.java +24 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.app.usage; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.usage.AppStandby.StandbyBuckets; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; Loading Loading @@ -246,6 +247,29 @@ public final class UsageStatsManager { } } /** * @hide */ public @StandbyBuckets int getAppStandbyBucket(String packageName) { try { return mService.getAppStandbyBucket(packageName, mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { } return AppStandby.STANDBY_BUCKET_ACTIVE; } /** * @hide */ public void setAppStandbyBucket(String packageName, @StandbyBuckets int bucket) { try { mService.setAppStandbyBucket(packageName, bucket, mContext.getUserId()); } catch (RemoteException e) { // Nothing to do } } /** * {@hide} * Temporarily whitelist the specified app for a short duration. This is to allow an app Loading
services/core/java/com/android/server/am/ActivityManagerShellCommand.java +25 −0 Original line number Diff line number Diff line Loading @@ -222,6 +222,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSetInactive(pw); case "get-inactive": return runGetInactive(pw); case "set-standby-bucket": return runSetStandbyBucket(pw); case "send-trim-memory": return runSendTrimMemory(pw); case "display": Loading Loading @@ -1824,6 +1826,27 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } int runSetStandbyBucket(PrintWriter pw) throws RemoteException { int userId = UserHandle.USER_CURRENT; String opt; while ((opt=getNextOption()) != null) { if (opt.equals("--user")) { userId = UserHandle.parseUserArg(getNextArgRequired()); } else { getErrPrintWriter().println("Error: Unknown option: " + opt); return -1; } } String packageName = getNextArgRequired(); String value = getNextArgRequired(); IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( Context.USAGE_STATS_SERVICE)); usm.setAppStandbyBucket(packageName, Integer.parseInt(value), userId); return 0; } int runGetInactive(PrintWriter pw) throws RemoteException { int userId = UserHandle.USER_CURRENT; Loading Loading @@ -2571,6 +2594,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Sets the inactive state of an app."); pw.println(" get-inactive [--user <USER_ID>] <PACKAGE>"); pw.println(" Returns the inactive state of an app."); pw.println(" set-standby-bucket [--user <USER_ID>] <PACKAGE> <BUCKET>"); pw.println(" Puts an app in the standby bucket."); pw.println(" send-trim-memory [--user <USER_ID>] <PROCESS>"); pw.println(" [HIDDEN|RUNNING_MODERATE|BACKGROUND|RUNNING_LOW|MODERATE|RUNNING_CRITICAL|COMPLETE]"); pw.println(" Send a memory trim event to a <PROCESS>. May also supply a raw trim int level."); Loading
services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +36 −21 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package com.android.server.usage; import static android.app.usage.AppStandby.REASON_TIMEOUT; import static android.app.usage.AppStandby.STANDBY_BUCKET_ACTIVE; import static android.app.usage.AppStandby.STANDBY_BUCKET_RARE; import android.app.usage.AppStandby; import android.os.FileUtils; import android.test.AndroidTestCase; Loading @@ -28,6 +33,8 @@ public class AppIdleHistoryTests extends AndroidTestCase { final static String PACKAGE_1 = "com.android.testpackage1"; final static String PACKAGE_2 = "com.android.testpackage2"; final static int USER_ID = 0; @Override protected void setUp() throws Exception { super.setUp(); Loading @@ -42,7 +49,6 @@ public class AppIdleHistoryTests extends AndroidTestCase { } public void testFilesCreation() { final int userId = 0; AppIdleHistory aih = new AppIdleHistory(mStorageDir, 0); aih.updateDisplay(true, /* elapsedRealtime= */ 1000); Loading @@ -50,9 +56,9 @@ public class AppIdleHistoryTests extends AndroidTestCase { // Screen On time file should be written right away assertTrue(aih.getScreenOnTimeFile().exists()); aih.writeAppIdleTimes(userId); aih.writeAppIdleTimes(USER_ID); // stats file should be written now assertTrue(new File(new File(mStorageDir, "users/" + userId), assertTrue(new File(new File(mStorageDir, "users/" + USER_ID), AppIdleHistory.APP_IDLE_FILENAME).exists()); } Loading @@ -77,24 +83,33 @@ public class AppIdleHistoryTests extends AndroidTestCase { assertEquals(aih2.getScreenOnTime(13000), 4000); } public void testPackageEvents() { public void testBuckets() { AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000); aih.setThresholds(4000, 1000); aih.updateDisplay(true, 1000); // App is not-idle by default assertFalse(aih.isIdle(PACKAGE_1, 0, 1500)); // Still not idle assertFalse(aih.isIdle(PACKAGE_1, 0, 3000)); // Idle now assertTrue(aih.isIdle(PACKAGE_1, 0, 8000)); // Not idle assertFalse(aih.isIdle(PACKAGE_2, 0, 9000)); // Screen off aih.updateDisplay(false, 9100); // Still idle after 10 seconds because screen hasn't been on long enough assertFalse(aih.isIdle(PACKAGE_2, 0, 20000)); aih.updateDisplay(true, 21000); assertTrue(aih.isIdle(PACKAGE_2, 0, 23000)); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE, AppStandby.REASON_USAGE); // ACTIVE means not idle assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000)); aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE, AppStandby.REASON_USAGE); aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE, REASON_TIMEOUT); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE); assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT); // RARE is considered idle assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000)); assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000)); // Check persistence aih.writeAppIdleDurations(); aih.writeAppIdleTimes(USER_ID); aih = new AppIdleHistory(mStorageDir, 4000); assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE); assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE); assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT); } } No newline at end of file