diff options
-rw-r--r-- | api/current.txt | 1 | ||||
-rw-r--r-- | api/system-current.txt | 1 | ||||
-rw-r--r-- | cmds/am/src/com/android/commands/am/Am.java | 51 | ||||
-rw-r--r-- | core/java/android/app/usage/IUsageStatsManager.aidl | 2 | ||||
-rw-r--r-- | core/java/android/app/usage/UsageStatsManager.java | 17 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 5 | ||||
-rw-r--r-- | packages/Shell/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | services/usage/java/com/android/server/usage/UsageStatsService.java | 100 | ||||
-rw-r--r-- | services/usage/java/com/android/server/usage/UserUsageStatsService.java | 11 |
9 files changed, 189 insertions, 0 deletions
diff --git a/api/current.txt b/api/current.txt index d29bd78..1ec8f5f 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<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long); method public java.util.List<android.app.usage.ConfigurationStats> 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 d6242ae..0acd415 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<java.lang.String, android.app.usage.UsageStats> queryAndAggregateUsageStats(long, long); method public java.util.List<android.app.usage.ConfigurationStats> 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 8ba2a5a..219d35b 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 <TASK_ID> [true|false]\n" + " am task resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>\n" + " am get-config\n" + + " am set-idle [--user <USER_ID>] <PACKAGE> true|false\n" + + " am get-idle [--user <USER_ID>] <PACKAGE>\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" + "<INTENT> specifications include these flags and arguments:\n" + " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" + " [-c <CATEGORY> [-c <CATEGORY>] ...]\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 4ed1489..23659e3 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 bc6099a..8a01d66 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 62685a1..559e452 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2097,6 +2097,11 @@ android:protectionLevel="signature|development|appop" /> <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <!-- @hide Allows an application to change the app idle state of an app. + <p>Not for use by third-party applications. --> + <permission android:name="android.permission.CHANGE_APP_IDLE_STATE" + android:protectionLevel="signature" /> + <!-- @SystemApi Allows an application to collect battery statistics --> <permission android:name="android.permission.BATTERY_STATS" android:protectionLevel="signature|system|development" /> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 35e9636..5b4b4fd 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -94,6 +94,7 @@ <uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" /> <uses-permission android:name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/> <uses-permission android:name="android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS" /> + <uses-permission android:name="android.permission.CHANGE_APP_IDLE_STATE" /> <application android:label="@string/app_label"> <provider diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 3d54dfb..edeeaba 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -17,7 +17,10 @@ package com.android.server.usage; import android.Manifest; +import android.app.ActivityManagerNative; +import android.app.AppGlobals; import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; import android.app.usage.ConfigurationStats; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; @@ -30,6 +33,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; @@ -82,6 +86,7 @@ public class UsageStatsService extends SystemService implements static final int MSG_FLUSH_TO_DISK = 1; static final int MSG_REMOVE_USER = 2; static final int MSG_INFORM_LISTENERS = 3; + static final int MSG_RESET_LAST_TIMESTAMP = 4; private final Object mLock = new Object(); Handler mHandler; @@ -279,6 +284,29 @@ public class UsageStatsService extends SystemService implements } /** + * Forces the app's timestamp to reflect idle or active. If idle, then it rolls back the + * last used timestamp to a point in time thats behind the threshold for idle. + */ + void resetLastTimestamp(String packageName, int userId, boolean idle) { + synchronized (mLock) { + final long timeNow = checkAndGetTimeLocked(); + final long lastTimestamp = timeNow - (idle ? mAppIdleDurationMillis : 0); + + final UserUsageStatsService service = + getUserDataAndInitializeIfNeededLocked(userId, timeNow); + final long lastUsed = service.getLastPackageAccessTime(packageName); + final boolean previouslyIdle = hasPassedIdleDuration(lastUsed); + service.setLastTimestamp(packageName, lastTimestamp); + // Inform listeners if necessary + if (previouslyIdle != idle) { + // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage); + mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId, + /* idle = */ idle ? 1 : 0, packageName)); + } + } + } + + /** * Called by the Binder stub. */ void flushToDisk() { @@ -384,13 +412,41 @@ public class UsageStatsService extends SystemService implements } boolean isAppIdle(String packageName, int userId) { + if (packageName == null) return false; if (SystemConfig.getInstance().getAllowInPowerSave().contains(packageName)) { return false; } + if (isActiveDeviceAdmin(packageName, userId)) { + return false; + } + final long lastUsed = getLastPackageAccessTime(packageName, userId); return hasPassedIdleDuration(lastUsed); } + void setAppIdle(String packageName, boolean idle, int userId) { + if (packageName == null) return; + + mHandler.obtainMessage(MSG_RESET_LAST_TIMESTAMP, userId, idle ? 1 : 0, packageName) + .sendToTarget(); + } + + private boolean isActiveDeviceAdmin(String packageName, int userId) { + DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class); + if (dpm == null) return false; + List<ComponentName> 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; @@ -566,6 +626,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) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 0a9481a..d94759d 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<UsageStats> sUsageStatsCombiner = new StatCombiner<UsageStats>() { @Override |