From cf76872a62075abf9008e99ca08413fb70761dc2 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Thu, 23 Apr 2015 20:36:41 -0700 Subject: [PATCH] Add ability to get and set idle state of apps Add am shell command to set and get idle Add public API to check if an app is idle Bug: 20534955 Bug: 20493806 Change-Id: Ib48b3fe847c71f05ef3905563f6e903cf060c498 --- api/current.txt | 1 + api/system-current.txt | 1 + cmds/am/src/com/android/commands/am/Am.java | 51 +++++++++ .../android/app/usage/IUsageStatsManager.aidl | 2 + .../android/app/usage/UsageStatsManager.java | 17 +++ core/res/AndroidManifest.xml | 5 + packages/Shell/AndroidManifest.xml | 1 + .../server/usage/UsageStatsService.java | 100 ++++++++++++++++++ .../server/usage/UserUsageStatsService.java | 11 ++ 9 files changed, 189 insertions(+) diff --git a/api/current.txt b/api/current.txt index d29bd78d6e974..1ec8f5fb4c88a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6131,6 +6131,7 @@ package android.app.usage { } public final class UsageStatsManager { + method public boolean isAppIdle(java.lang.String); method public java.util.Map queryAndAggregateUsageStats(long, long); method public java.util.List queryConfigurations(int, long, long); method public android.app.usage.UsageEvents queryEvents(long, long); diff --git a/api/system-current.txt b/api/system-current.txt index d6242ae32347b..0acd4154ce8ae 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6319,6 +6319,7 @@ package android.app.usage { } public final class UsageStatsManager { + method public boolean isAppIdle(java.lang.String); method public java.util.Map queryAndAggregateUsageStats(long, long); method public java.util.List queryConfigurations(int, long, long); method public android.app.usage.UsageEvents queryEvents(long, long); diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 8ba2a5ac6b552..219d35bd50cbc 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -142,6 +142,8 @@ public class Am extends BaseCommand { " am task resizeable [true|false]\n" + " am task resize \n" + " am get-config\n" + + " am set-idle [--user ] true|false\n" + + " am get-idle [--user ] \n" + "\n" + "am start: start an Activity. Options are:\n" + " -D: enable debugging\n" + @@ -282,6 +284,11 @@ public class Am extends BaseCommand { "am get-config: retrieve the configuration and any recent configurations\n" + " of the device\n" + "\n" + + "am set-idle: sets the idle state of an app\n" + + "\n" + + "am get-idle: returns the idle state of an app\n" + + "\n" + + "\n" + " specifications include these flags and arguments:\n" + " [-a ] [-d ] [-t ]\n" + " [-c [-c ] ...]\n" + @@ -388,6 +395,10 @@ public class Am extends BaseCommand { runTask(); } else if (op.equals("get-config")) { runGetConfig(); + } else if (op.equals("set-idle")) { + runSetIdle(); + } else if (op.equals("get-idle")) { + runGetIdle(); } else { showError("Error: unknown command '" + op + "'"); } @@ -2019,6 +2030,46 @@ public class Am extends BaseCommand { } } + private void runSetIdle() throws Exception { + int userId = UserHandle.USER_OWNER; + + String opt; + while ((opt=nextOption()) != null) { + if (opt.equals("--user")) { + userId = parseUserArg(nextArgRequired()); + } else { + System.err.println("Error: Unknown option: " + opt); + return; + } + } + String packageName = nextArgRequired(); + String value = nextArgRequired(); + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + usm.setAppIdle(packageName, Boolean.parseBoolean(value), userId); + } + + private void runGetIdle() throws Exception { + int userId = UserHandle.USER_OWNER; + + String opt; + while ((opt=nextOption()) != null) { + if (opt.equals("--user")) { + userId = parseUserArg(nextArgRequired()); + } else { + System.err.println("Error: Unknown option: " + opt); + return; + } + } + String packageName = nextArgRequired(); + + IUsageStatsManager usm = IUsageStatsManager.Stub.asInterface(ServiceManager.getService( + Context.USAGE_STATS_SERVICE)); + boolean isIdle = usm.isAppIdle(packageName, userId); + System.out.println("Idle=" + isIdle); + } + /** * Open the given file for sending into the system process. This verifies * with SELinux that the system will have access to the file. diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 4ed148911a434..23659e389afce 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -30,4 +30,6 @@ interface IUsageStatsManager { ParceledListSlice queryConfigurationStats(int bucketType, long beginTime, long endTime, String callingPackage); UsageEvents queryEvents(long beginTime, long endTime, String callingPackage); + void setAppIdle(String packageName, boolean idle, int userId); + boolean isAppIdle(String packageName, int userId); } diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index bc6099a7d5e35..8a01d663055cc 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -19,6 +19,7 @@ package android.app.usage; import android.content.Context; import android.content.pm.ParceledListSlice; import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; import java.util.Collections; @@ -217,4 +218,20 @@ public final class UsageStatsManager { } return aggregatedStats; } + + /** + * Returns whether the specified app is currently considered idle. This will be true if the + * app hasn't been used directly or indirectly for a period of time defined by the system. This + * could be of the order of several hours or days. + * @param packageName The package name of the app to query + * @return whether the app is currently considered idle + */ + public boolean isAppIdle(String packageName) { + try { + return mService.isAppIdle(packageName, UserHandle.myUserId()); + } catch (RemoteException e) { + // fall through and return default + } + return false; + } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 62685a1898f7a..559e45228210f 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2097,6 +2097,11 @@ android:protectionLevel="signature|development|appop" /> + + + diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 35e9636f3649f..5b4b4fd00b76b 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -94,6 +94,7 @@ + components = dpm.getActiveAdminsAsUser(userId); + if (components == null) { + return false; + } + final int size = components.size(); + for (int i = 0; i < size; i++) { + if (components.get(i).getPackageName().equals(packageName)) { + return true; + } + } + return false; + } + void informListeners(String packageName, int userId, boolean isIdle) { for (AppIdleStateChangeListener listener : mPackageAccessListeners) { listener.onAppIdleStateChanged(packageName, userId, isIdle); @@ -459,6 +515,10 @@ public class UsageStatsService extends SystemService implements informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1); break; + case MSG_RESET_LAST_TIMESTAMP: + resetLastTimestamp((String) msg.obj, msg.arg1, msg.arg2 == 1); + break; + default: super.handleMessage(msg); break; @@ -565,6 +625,46 @@ public class UsageStatsService extends SystemService implements } } + @Override + public boolean isAppIdle(String packageName, int userId) { + try { + userId = ActivityManagerNative.getDefault().handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, true, "isAppIdle", null); + } catch (RemoteException re) { + return false; + } + final long token = Binder.clearCallingIdentity(); + try { + return UsageStatsService.this.isAppIdle(packageName, userId); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void setAppIdle(String packageName, boolean idle, int userId) { + final int callingUid = Binder.getCallingUid(); + try { + userId = ActivityManagerNative.getDefault().handleIncomingUser( + Binder.getCallingPid(), callingUid, userId, false, true, + "setAppIdle", null); + } catch (RemoteException re) { + return; + } + getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE, + "No permission to change app idle state"); + final long token = Binder.clearCallingIdentity(); + try { + PackageInfo pi = AppGlobals.getPackageManager() + .getPackageInfo(packageName, 0, userId); + if (pi == null) return; + UsageStatsService.this.setAppIdle(packageName, idle, userId); + } catch (RemoteException re) { + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 0a9481acf70c6..d94759d0a6854 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -211,6 +211,17 @@ class UserUsageStatsService { notifyStatsChanged(); } + /** + * Sets the last timestamp for each of the intervals. + * @param lastTimestamp + */ + void setLastTimestamp(String packageName, long lastTimestamp) { + for (IntervalStats stats : mCurrentStats) { + stats.update(packageName, lastTimestamp, UsageEvents.Event.NONE); + } + notifyStatsChanged(); + } + private static final StatCombiner sUsageStatsCombiner = new StatCombiner() { @Override