diff options
author | 2015-03-04 09:56:14 -0800 | |
---|---|---|
committer | 2015-04-03 13:20:19 -0700 | |
commit | b0ff32245cb6b51e43dd3ee40b86d683c62de2b9 (patch) | |
tree | caa4ccf39aced5bf9ebcca0386b8948aac3302fb | |
parent | 73e5f13d9919d773a2de12f3b576c9b56a54b173 (diff) |
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
10 files changed, 412 insertions, 10 deletions
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 0122069cf966..8b3fc2e4337e 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -57,4 +57,41 @@ public abstract class UsageStatsManagerInternal { * Prepares the UsageStatsService for shutdown. */ public abstract void prepareShutdown(); + + /** + * Returns true if the app has not been used for a certain amount of time. How much time? + * Could be hours, could be days, who knows? + * + * @param packageName + * @param userId + * @return + */ + public abstract boolean isAppIdle(String packageName, int userId); + + /** + * Returns the most recent time that the specified package was active for the given user. + * @param packageName The package to search. + * @param userId The user id of the user of interest. + * @return The timestamp of when the package was last used, or -1 if it hasn't been used. + */ + public abstract long getLastPackageAccessTime(String packageName, int userId); + + /** + * Sets up a listener for changes to packages being accessed. + * @param listener A listener within the system process. + */ + public abstract void addAppIdleStateChangeListener( + AppIdleStateChangeListener listener); + + /** + * Removes a listener that was previously added for package usage state changes. + * @param listener The listener within the system process to remove. + */ + public abstract void removeAppIdleStateChangeListener( + AppIdleStateChangeListener listener); + + public interface AppIdleStateChangeListener { + void onAppIdleStateChanged(String packageName, int userId, boolean idle); + } + } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ec7e8b2a6ab8..f488ea0a877f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3026,7 +3026,7 @@ public final class Settings { }; /** - * These are all pulbic system settings + * These are all public system settings * * @hide */ @@ -3126,7 +3126,7 @@ public final class Settings { } /** - * These are all pulbic system settings + * These are all public system settings * * @hide */ @@ -5363,6 +5363,13 @@ public final class Settings { public static final String SLEEP_TIMEOUT = "sleep_timeout"; /** + * Duration in milliseconds that an app should be inactive before it is considered idle. + * <p/>Type: Long + * @hide + */ + public static final String APP_IDLE_DURATION = "app_idle_duration"; + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -5425,6 +5432,7 @@ public final class Settings { * since the managed profile doesn't get to change them. */ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); + static { CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED); CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION); diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 7a74e459ebf7..c2b0a4d426f0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -73,6 +73,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false; static final boolean DEBUG_USER_LEAVING = DEBUG_ALL || false; static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false; + static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || true; static final String POSTFIX_BACKUP = (APPEND_CATEGORY_NAME) ? "_Backup" : ""; static final String POSTFIX_BROADCAST = (APPEND_CATEGORY_NAME) ? "_Broadcast" : ""; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b0b410bfbbcf..cdaa5a38da6a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -43,6 +43,7 @@ import android.app.ITaskStackListener; import android.app.ProfilerInfo; import android.app.admin.DevicePolicyManager; import android.app.usage.UsageEvents; +import android.app.usage.UsageStats; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; import android.content.res.Resources; @@ -61,8 +62,8 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.SparseIntArray; - import android.view.Display; + import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.DumpHeapActivity; @@ -96,7 +97,6 @@ import com.android.server.pm.UserManagerService; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.AppTransition; import com.android.server.wm.WindowManagerService; - import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -17854,6 +17854,10 @@ public final class ActivityManagerService extends ActivityManagerNative app.lastCpuTime = app.curCpuTime; } + // Inform UsageStats of important process state change + // Must be called before updating setProcState + maybeUpdateUsageStats(app); + app.setProcState = app.curProcState; if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) { app.notCachedSinceIdle = false; @@ -17916,6 +17920,28 @@ public final class ActivityManagerService extends ActivityManagerNative return success; } + private void maybeUpdateUsageStats(ProcessRecord app) { + if (DEBUG_USAGE_STATS) { + Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList()) + + "] state changes: old = " + app.setProcState + ", new = " + + app.curProcState); + } + if (mUsageStatsService == null) { + return; + } + if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + && (app.setProcState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + || app.setProcState < 0)) { + String[] packages = app.getPackageList(); + if (packages != null) { + for (int i = 0; i < packages.length; i++) { + mUsageStatsService.reportEvent(packages[i], app.userId, + UsageEvents.Event.INTERACTION); + } + } + } + } + private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) { if (proc.thread != null) { if (proc.baseProcessTracker != null) { diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index d79b5fd21d7b..ecda36a0d4c6 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -51,6 +51,7 @@ import android.util.Slog; import android.util.SparseArray; import com.android.internal.app.IBatteryStats; +import com.android.server.job.controllers.AppIdleController; import com.android.server.job.controllers.BatteryController; import com.android.server.job.controllers.ConnectivityController; import com.android.server.job.controllers.IdleController; @@ -317,6 +318,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.add(TimeController.get(this)); mControllers.add(IdleController.get(this)); mControllers.add(BatteryController.get(this)); + mControllers.add(AppIdleController.get(this)); mHandler = new JobHandler(context.getMainLooper()); mJobSchedulerStub = new JobSchedulerStub(); @@ -688,7 +690,6 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean jobPending = mPendingJobs.contains(job); final boolean jobActive = isCurrentlyActiveLocked(job); final boolean userRunning = mStartedUsers.contains(job.getUserId()); - if (DEBUG) { Slog.v(TAG, "isReadyToBeExecutedLocked: " + job.toShortString() + " ready=" + jobReady + " pending=" + jobPending @@ -738,6 +739,10 @@ public class JobSchedulerService extends com.android.server.SystemService } } if (availableContext != null) { + if (DEBUG) { + Slog.d(TAG, "About to run job " + + nextPending.getJob().getService().toString()); + } if (!availableContext.executeRunnableJob(nextPending)) { if (DEBUG) { Slog.d(TAG, "Error executing " + nextPending); diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java new file mode 100644 index 000000000000..03e9ad5af31c --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.job.controllers; + +import android.app.usage.UsageStatsManagerInternal; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +import android.os.BatteryManagerInternal; +import android.util.Slog; + +import com.android.server.LocalServices; +import com.android.server.job.JobSchedulerService; +import com.android.server.job.StateChangedListener; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controls when apps are considered idle and if jobs pertaining to those apps should + * be executed. Apps that haven't been actively launched or accessed from a foreground app + * for a certain amount of time (maybe hours or days) are considered idle. When the app comes + * out of idle state, it will be allowed to run scheduled jobs. + */ +public class AppIdleController extends StateController + implements UsageStatsManagerInternal.AppIdleStateChangeListener { + + private static final String LOG_TAG = "AppIdleController"; + private static final boolean DEBUG = true; + + // Singleton factory + private static Object sCreationLock = new Object(); + private static volatile AppIdleController sController; + final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); + private final UsageStatsManagerInternal mUsageStatsInternal; + private final BatteryManagerInternal mBatteryManagerInternal; + private boolean mPluggedIn; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); + // TODO: Allow any charger type + onPluggedIn((plugged & BatteryManager.BATTERY_PLUGGED_AC) != 0); + } + } + }; + + public static AppIdleController get(JobSchedulerService service) { + synchronized (sCreationLock) { + if (sController == null) { + sController = new AppIdleController(service, service.getContext()); + } + return sController; + } + } + + private AppIdleController(StateChangedListener stateChangedListener, Context context) { + super(stateChangedListener, context); + mUsageStatsInternal = LocalServices.getService(UsageStatsManagerInternal.class); + mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); + mPluggedIn = isPowered(); + mUsageStatsInternal.addAppIdleStateChangeListener(this); + registerReceivers(); + } + + private void registerReceivers() { + // Monitor battery charging state + IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(mReceiver, filter); + } + + private boolean isPowered() { + // TODO: Allow any charger type + return mBatteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_AC); + } + + @Override + public void maybeStartTrackingJob(JobStatus jobStatus) { + synchronized (mTrackedTasks) { + mTrackedTasks.add(jobStatus); + String packageName = jobStatus.job.getService().getPackageName(); + final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, + jobStatus.getUserId()); + if (DEBUG) { + Slog.d(LOG_TAG, "Start tracking, setting idle state of " + + packageName + " to " + appIdle); + } + jobStatus.appNotIdleConstraintSatisfied.set(!appIdle); + } + } + + @Override + public void maybeStopTrackingJob(JobStatus jobStatus) { + synchronized (mTrackedTasks) { + mTrackedTasks.remove(jobStatus); + } + } + + @Override + public void dumpControllerState(PrintWriter pw) { + // TODO: + } + + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle) { + boolean changed = false; + synchronized (mTrackedTasks) { + // If currently plugged in, we don't care about app idle state + if (mPluggedIn) { + return; + } + for (JobStatus task : mTrackedTasks) { + if (task.job.getService().getPackageName().equals(packageName) + && task.getUserId() == userId) { + if (task.appNotIdleConstraintSatisfied.get() != !idle) { + if (DEBUG) { + Slog.d(LOG_TAG, "App Idle state changed, setting idle state of " + + packageName + " to " + idle); + } + task.appNotIdleConstraintSatisfied.set(!idle); + changed = true; + } + } + } + } + if (changed) { + mStateChangedListener.onControllerStateChanged(); + } + } + + void onPluggedIn(boolean pluggedIn) { + // Flag if any app's idle state has changed + boolean changed = false; + synchronized (mTrackedTasks) { + if (mPluggedIn == pluggedIn) { + return; + } + mPluggedIn = pluggedIn; + for (JobStatus task : mTrackedTasks) { + String packageName = task.job.getService().getPackageName(); + final boolean appIdle = !mPluggedIn && mUsageStatsInternal.isAppIdle(packageName, + task.getUserId()); + if (DEBUG) { + Slog.d(LOG_TAG, "Plugged in " + pluggedIn + ", setting idle state of " + + packageName + " to " + appIdle); + } + if (task.appNotIdleConstraintSatisfied.get() == appIdle) { + task.appNotIdleConstraintSatisfied.set(!appIdle); + changed = true; + } + } + } + if (changed) { + mStateChangedListener.onControllerStateChanged(); + } + } +} diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java index e3c55b62b8a5..69c63f399a76 100644 --- a/services/core/java/com/android/server/job/controllers/JobStatus.java +++ b/services/core/java/com/android/server/job/controllers/JobStatus.java @@ -54,6 +54,7 @@ public class JobStatus { final AtomicBoolean idleConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean unmeteredConstraintSatisfied = new AtomicBoolean(); final AtomicBoolean connectivityConstraintSatisfied = new AtomicBoolean(); + final AtomicBoolean appNotIdleConstraintSatisfied = new AtomicBoolean(); /** * Earliest point in the future at which this job will be eligible to run. A value of 0 @@ -199,8 +200,11 @@ public class JobStatus { * the constraints are satisfied <strong>or</strong> the deadline on the job has expired. */ public synchronized boolean isReady() { - return isConstraintsSatisfied() - || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get()); + // Deadline constraint trumps other constraints + // AppNotIdle implicit constraint trumps all! + return (isConstraintsSatisfied() + || (hasDeadlineConstraint() && deadlineConstraintSatisfied.get())) + && appNotIdleConstraintSatisfied.get(); } /** @@ -229,6 +233,7 @@ public class JobStatus { + ",N=" + job.getNetworkType() + ",C=" + job.isRequireCharging() + ",I=" + job.isRequireDeviceIdle() + ",F=" + numFailures + ",P=" + job.isPersisted() + + ",ANI=" + appNotIdleConstraintSatisfied.get() + (isReady() ? "(READY)" : "") + "]"; } diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java index efd192844c34..cda7c326c37d 100644 --- a/services/core/java/com/android/server/job/controllers/StateController.java +++ b/services/core/java/com/android/server/job/controllers/StateController.java @@ -44,7 +44,7 @@ public abstract class StateController { /** * Implement the logic here to decide whether a job should be tracked by this controller. - * This logic is put here so the JobManger can be completely agnostic of Controller logic. + * This logic is put here so the JobManager can be completely agnostic of Controller logic. * Also called when updating a task, so implementing controllers have to be aware of * preexisting tasks. */ diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 5eefe6ab2b4c..f458dbcebbc4 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 75fa0303e753..afe27c75038b 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"); |