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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user