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
This commit is contained in:
Amith Yamasani
2015-04-23 20:36:41 -07:00
parent a1ab2d37a0
commit cf76872a62
9 changed files with 189 additions and 0 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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" />

View File

@@ -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

View File

@@ -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;
@@ -278,6 +283,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.
*/
@@ -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;
@@ -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)

View File

@@ -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