summaryrefslogtreecommitdiffstats
path: root/services/usage/java/com/android/server
diff options
context:
space:
mode:
authorAmith Yamasani <yamasani@google.com>2015-03-04 09:56:14 -0800
committerAmith Yamasani <yamasani@google.com>2015-04-03 13:20:19 -0700
commitb0ff32245cb6b51e43dd3ee40b86d683c62de2b9 (patch)
treecaa4ccf39aced5bf9ebcca0386b8948aac3302fb /services/usage/java/com/android/server
parent73e5f13d9919d773a2de12f3b576c9b56a54b173 (diff)
downloadframeworks_base-b0ff32245cb6b51e43dd3ee40b86d683c62de2b9.zip
frameworks_base-b0ff32245cb6b51e43dd3ee40b86d683c62de2b9.tar.gz
frameworks_base-b0ff32245cb6b51e43dd3ee40b86d683c62de2b9.tar.bz2
Throttle jobs for idle apps
First pass at delaying jobs from apps that are idle. TODO: Throttle syncs TODO: Provide a periodic point at which apps are checked for idleness. Apps that switch to foreground process state are tracked by UsageStats as an INTERACTION event that affects the last-used timestamp. JobScheduler's logic for when an app is ready is trumped by the idleness of the app, and only if the battery is not charging. When charging state changes, we update the idle state of all the tracked jobs. android package is whitelisted. Bug: 20066058 Change-Id: I0a0acb517b100a5c7b11e3f435f4141375f3451f
Diffstat (limited to 'services/usage/java/com/android/server')
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java133
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java17
2 files changed, 148 insertions, 2 deletions
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 5eefe6a..f458dbc 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -21,8 +21,10 @@ import android.app.AppOpsManager;
import android.app.usage.ConfigurationStats;
import android.app.usage.IUsageStatsManager;
import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStats;
import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -32,6 +34,8 @@ import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
@@ -42,6 +46,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -53,6 +58,7 @@ import com.android.server.SystemService;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -74,6 +80,7 @@ public class UsageStatsService extends SystemService implements
static final int MSG_REPORT_EVENT = 0;
static final int MSG_FLUSH_TO_DISK = 1;
static final int MSG_REMOVE_USER = 2;
+ static final int MSG_INFORM_LISTENERS = 3;
private final Object mLock = new Object();
Handler mHandler;
@@ -85,6 +92,12 @@ public class UsageStatsService extends SystemService implements
long mRealTimeSnapshot;
long mSystemTimeSnapshot;
+ private static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = 3L * 24 * 60 * 60 * 1000; //3 days
+ private long mAppIdleDurationMillis;
+
+ private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+ mPackageAccessListeners = new ArrayList<>();
+
public UsageStatsService(Context context) {
super(context);
}
@@ -112,11 +125,24 @@ public class UsageStatsService extends SystemService implements
mRealTimeSnapshot = SystemClock.elapsedRealtime();
mSystemTimeSnapshot = System.currentTimeMillis();
+ // Look at primary user's secure setting for this. TODO: Maybe apply different
+ // thresholds for different users.
+ mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(),
+ Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS,
+ UserHandle.USER_OWNER);
publishLocalService(UsageStatsManagerInternal.class, new LocalService());
publishBinderService(Context.USAGE_STATS_SERVICE, new BinderService());
}
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ // Observe changes to the threshold
+ new SettingsObserver(mHandler).registerObserver();
+ }
+ }
+
private class UserRemovedReceiver extends BroadcastReceiver {
@Override
@@ -235,7 +261,19 @@ public class UsageStatsService extends SystemService implements
final UserUsageStatsService service =
getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+ final long lastUsed = service.getLastPackageAccessTime(event.mPackage);
+ final boolean previouslyIdle = hasPassedIdleDuration(lastUsed);
service.reportEvent(event);
+ // Inform listeners if necessary
+ if ((event.mEventType == Event.MOVE_TO_FOREGROUND
+ || event.mEventType == Event.MOVE_TO_BACKGROUND
+ || event.mEventType == Event.INTERACTION)) {
+ if (previouslyIdle) {
+ // Slog.d(TAG, "Informing listeners of out-of-idle " + event.mPackage);
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+ /* idle = */ 0, event.mPackage));
+ }
+ }
}
}
@@ -308,6 +346,53 @@ public class UsageStatsService extends SystemService implements
}
}
+ /**
+ * Called by LocalService stub.
+ */
+ long getLastPackageAccessTime(String packageName, int userId) {
+ synchronized (mLock) {
+ final long timeNow = checkAndGetTimeLocked();
+ // android package is always considered non-idle.
+ // TODO: Add a generic whitelisting mechanism
+ if (packageName.equals("android")) {
+ return timeNow;
+ }
+ final UserUsageStatsService service =
+ getUserDataAndInitializeIfNeededLocked(userId, timeNow);
+ return service.getLastPackageAccessTime(packageName);
+ }
+ }
+
+ void addListener(AppIdleStateChangeListener listener) {
+ synchronized (mLock) {
+ if (!mPackageAccessListeners.contains(listener)) {
+ mPackageAccessListeners.add(listener);
+ }
+ }
+ }
+
+ void removeListener(AppIdleStateChangeListener listener) {
+ synchronized (mLock) {
+ mPackageAccessListeners.remove(listener);
+ }
+ }
+
+ private boolean hasPassedIdleDuration(long lastUsed) {
+ final long now = System.currentTimeMillis();
+ return lastUsed < now - mAppIdleDurationMillis;
+ }
+
+ boolean isAppIdle(String packageName, int userId) {
+ final long lastUsed = getLastPackageAccessTime(packageName, userId);
+ return hasPassedIdleDuration(lastUsed);
+ }
+
+ void informListeners(String packageName, int userId, boolean isIdle) {
+ for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
+ listener.onAppIdleStateChanged(packageName, userId, isIdle);
+ }
+ }
+
private static boolean validRange(long currentTime, long beginTime, long endTime) {
return beginTime <= currentTime && beginTime < endTime;
}
@@ -366,6 +451,10 @@ public class UsageStatsService extends SystemService implements
removeUser(msg.arg1);
break;
+ case MSG_INFORM_LISTENERS:
+ informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
+ break;
+
default:
super.handleMessage(msg);
break;
@@ -373,6 +462,29 @@ public class UsageStatsService extends SystemService implements
}
}
+ /**
+ * Observe settings changes for Settings.Secure.APP_IDLE_DURATION.
+ */
+ private class SettingsObserver extends ContentObserver {
+
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void registerObserver() {
+ getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+ Settings.Secure.APP_IDLE_DURATION), false, this, UserHandle.USER_OWNER);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ mAppIdleDurationMillis = Settings.Secure.getLongForUser(getContext().getContentResolver(),
+ Settings.Secure.APP_IDLE_DURATION, DEFAULT_APP_IDLE_THRESHOLD_MILLIS,
+ UserHandle.USER_OWNER);
+ // TODO: Check if we need to update idle states of all the apps
+ }
+ }
+
private class BinderService extends IUsageStatsManager.Stub {
private boolean hasPermission(String callingPackage) {
@@ -523,11 +635,32 @@ public class UsageStatsService extends SystemService implements
}
@Override
+ public boolean isAppIdle(String packageName, int userId) {
+ return UsageStatsService.this.isAppIdle(packageName, userId);
+ }
+
+ @Override
+ public long getLastPackageAccessTime(String packageName, int userId) {
+ return UsageStatsService.this.getLastPackageAccessTime(packageName, userId);
+ }
+
+ @Override
public void prepareShutdown() {
// This method *WILL* do IO work, but we must block until it is finished or else
// we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
// we are shutting down.
shutdown();
}
+
+ @Override
+ public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
+ UsageStatsService.this.addListener(listener);
+ }
+
+ @Override
+ public void removeAppIdleStateChangeListener(
+ AppIdleStateChangeListener listener) {
+ UsageStatsService.this.removeListener(listener);
+ }
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 75fa030..afe27c7 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -65,7 +65,8 @@ class UserUsageStatsService {
void onStatsUpdated();
}
- UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener) {
+ UserUsageStatsService(Context context, int userId, File usageStatsDir,
+ StatsUpdatedListener listener) {
mContext = context;
mDailyExpiryDate = new UnixCalendar(0);
mDatabase = new UsageStatsDatabase(usageStatsDir);
@@ -161,7 +162,9 @@ class UserUsageStatsService {
if (currentDailyStats.events == null) {
currentDailyStats.events = new TimeSparseArray<>();
}
- currentDailyStats.events.put(event.mTimeStamp, event);
+ if (event.mEventType != UsageEvents.Event.INTERACTION) {
+ currentDailyStats.events.put(event.mTimeStamp, event);
+ }
for (IntervalStats stats : mCurrentStats) {
if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
@@ -328,6 +331,16 @@ class UserUsageStatsService {
return new UsageEvents(results, table);
}
+ long getLastPackageAccessTime(String packageName) {
+ final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
+ UsageStats packageUsage;
+ if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
+ return -1;
+ } else {
+ return packageUsage.getLastTimeUsed();
+ }
+ }
+
void persistActiveStats() {
if (mStatsChanged) {
Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");