summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmith Yamasani <yamasani@google.com>2015-04-23 20:36:41 -0700
committerAmith Yamasani <yamasani@google.com>2015-04-24 12:58:46 -0700
commitcf76872a62075abf9008e99ca08413fb70761dc2 (patch)
tree20ba4867939d30bccefe383ae85848bd610e0dde
parenta1ab2d37a055f9c2d623260b41ecff778fdf3aa2 (diff)
downloadframeworks_base-cf76872a62075abf9008e99ca08413fb70761dc2.zip
frameworks_base-cf76872a62075abf9008e99ca08413fb70761dc2.tar.gz
frameworks_base-cf76872a62075abf9008e99ca08413fb70761dc2.tar.bz2
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
-rw-r--r--api/current.txt1
-rw-r--r--api/system-current.txt1
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java51
-rw-r--r--core/java/android/app/usage/IUsageStatsManager.aidl2
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java17
-rw-r--r--core/res/AndroidManifest.xml5
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java100
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java11
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