From 520d8f2ac6ad2c3cd244e1f710103b3a43a41725 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Fri, 8 May 2015 16:36:21 -0700 Subject: [PATCH] Allow exemption to idle apps at periodic intervals Triggers are device idle mode changing as well as internal delayed message handlers. Bug: 20066058 Change-Id: I0627cfbcc16cfc2b8ac7d298fd2c681a5a6571dd --- .../com/android/server/DeviceIdleController.java | 23 +++-- .../android/server/usage/UsageStatsService.java | 108 ++++++++++++++++++++- .../server/usage/UserUsageStatsService.java | 17 +++- 3 files changed, 132 insertions(+), 16 deletions(-) diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 0b33812f12f8..5dbf4c9e6565 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -45,17 +45,20 @@ import android.os.SystemClock; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.Xml; import android.view.Display; + import com.android.internal.app.IBatteryStats; import com.android.internal.os.AtomicFile; import com.android.internal.os.BackgroundThread; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -75,6 +78,8 @@ import java.io.PrintWriter; public class DeviceIdleController extends SystemService { private static final String TAG = "DeviceIdleController"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + public static final String SERVICE_NAME = "deviceidle"; private static final String ACTION_STEP_IDLE_STATE = @@ -88,17 +93,20 @@ public class DeviceIdleController extends SystemService { * immediately after going inactive just because we don't want to be continually running * the significant motion sensor whenever the screen is off. */ - private static final long DEFAULT_INACTIVE_TIMEOUT = 30*60*1000L; + private static final long DEFAULT_INACTIVE_TIMEOUT = !DEBUG ? 30*60*1000L + : 2 * 60 * 1000L; /** * This is the time, after seeing motion, that we wait after becoming inactive from * that until we start looking for motion again. */ - private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = 10*60*1000L; + private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = !DEBUG ? 10*60*1000L + : 60 * 1000L; /** * This is the time, after the inactive timeout elapses, that we will wait looking * for significant motion until we truly consider the device to be idle. */ - private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = 30*60*1000L; + private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = !DEBUG ? 30*60*1000L + : 2 * 60 * 1000L; /** * This is the initial time, after being idle, that we will allow ourself to be back * in the IDLE_PENDING state allowing the system to run normally until we return to idle. @@ -117,11 +125,13 @@ public class DeviceIdleController extends SystemService { * This is the initial time that we want to sit in the idle state before waking up * again to return to pending idle and allowing normal work to run. */ - private static final long DEFAULT_IDLE_TIMEOUT = 60*60*1000L; + private static final long DEFAULT_IDLE_TIMEOUT = !DEBUG ? 60*60*1000L + : 5 * 60 * 1000L; /** * Maximum idle duration we will be allowed to use. */ - private static final long DEFAULT_MAX_IDLE_TIMEOUT = 6*60*60*1000L; + private static final long DEFAULT_MAX_IDLE_TIMEOUT = !DEBUG ? 6*60*60*1000L + : 10 * 60 * 1000L; /** * Scaling factor to apply to current idle timeout each time we cycle through that state. */ @@ -130,7 +140,8 @@ public class DeviceIdleController extends SystemService { * This is the minimum time we will allow until the next upcoming alarm for us to * actually go in to idle mode. */ - private static final long DEFAULT_MIN_TIME_TO_ALARM = 60*60*1000L; + private static final long DEFAULT_MIN_TIME_TO_ALARM = !DEBUG ? 60*60*1000L + : 5 * 60 * 1000L; private AlarmManager mAlarmManager; private IBatteryStats mBatteryStats; diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 197daedae39a..d18f7c245dbf 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -49,6 +49,7 @@ import android.os.Handler; import android.os.IDeviceIdleController; import android.os.Looper; import android.os.Message; +import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -58,6 +59,7 @@ import android.os.UserManager; import android.provider.Settings; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -87,13 +89,21 @@ public class UsageStatsService extends SystemService implements static final String TAG = "UsageStatsService"; - static final boolean DEBUG = false; + static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final long TEN_SECONDS = 10 * 1000; + private static final long ONE_MINUTE = 60 * 1000; private static final long TWENTY_MINUTES = 20 * 60 * 1000; private static final long FLUSH_INTERVAL = DEBUG ? TEN_SECONDS : TWENTY_MINUTES; private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds. - static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = 2L * 24 * 60 * 60 * 1000; // 1 day - static final long DEFAULT_CHECK_IDLE_INTERVAL = 8 * 3600 * 1000; // 8 hours + + static final long DEFAULT_APP_IDLE_THRESHOLD_MILLIS = DEBUG ? ONE_MINUTE * 4 + : 1L * 24 * 60 * 60 * 1000; // 1 day + static final long DEFAULT_CHECK_IDLE_INTERVAL = DEBUG ? ONE_MINUTE + : 8 * 3600 * 1000; // 8 hours + static final long DEFAULT_PAROLE_INTERVAL = DEBUG ? ONE_MINUTE * 10 + : 24 * 60 * 60 * 1000L; // 24 hours between paroles + static final long DEFAULT_PAROLE_DURATION = DEBUG ? ONE_MINUTE * 2 + : 10 * 60 * 1000L; // 10 minutes // Handler message types. static final int MSG_REPORT_EVENT = 0; @@ -102,6 +112,8 @@ public class UsageStatsService extends SystemService implements static final int MSG_INFORM_LISTENERS = 3; static final int MSG_FORCE_IDLE_STATE = 4; static final int MSG_CHECK_IDLE_STATES = 5; + static final int MSG_CHECK_PAROLE_TIMEOUT = 6; + static final int MSG_PAROLE_END_TIMEOUT = 7; private final Object mLock = new Object(); Handler mHandler; @@ -110,14 +122,16 @@ public class UsageStatsService extends SystemService implements AppWidgetManager mAppWidgetManager; IDeviceIdleController mDeviceIdleController; private DisplayManager mDisplayManager; + private PowerManager mPowerManager; private final SparseArray mUserState = new SparseArray<>(); private File mUsageStatsDir; long mRealTimeSnapshot; long mSystemTimeSnapshot; + boolean mAppIdleParoled; private boolean mScreenOn; - + private long mLastAppIdleParoledTime; long mAppIdleDurationMillis; long mCheckIdleIntervalMillis = DEFAULT_CHECK_IDLE_INTERVAL; long mScreenOnTime; @@ -152,6 +166,7 @@ public class UsageStatsService extends SystemService implements IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); + deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); getContext().registerReceiver(new DeviceStateReceiver(), deviceStates); synchronized (mLock) { cleanUpRemovedUsersLocked(); @@ -179,6 +194,8 @@ public class UsageStatsService extends SystemService implements ServiceManager.getService(DeviceIdleController.SERVICE_NAME)); mDisplayManager = (DisplayManager) getContext().getSystemService( Context.DISPLAY_SERVICE); + mPowerManager = getContext().getSystemService(PowerManager.class); + mScreenOnSystemTimeSnapshot = System.currentTimeMillis(); synchronized (this) { mScreenOnTime = readScreenOnTimeLocked(); @@ -216,6 +233,8 @@ public class UsageStatsService extends SystemService implements if (BatteryManager.ACTION_CHARGING.equals(action) || BatteryManager.ACTION_DISCHARGING.equals(action)) { setAppIdleParoled(BatteryManager.ACTION_CHARGING.equals(action)); + } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) { + onDeviceIdleModeChanged(); } } } @@ -270,15 +289,41 @@ public class UsageStatsService extends SystemService implements } } + /** Paroled here means temporary pardon from being inactive */ void setAppIdleParoled(boolean paroled) { synchronized (mLock) { if (mAppIdleParoled != paroled) { mAppIdleParoled = paroled; + if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleParoled); + if (paroled) { + mLastAppIdleParoledTime = checkAndGetTimeLocked(); + postNextParoleTimeout(); + } postCheckIdleStates(); } } } + private void postNextParoleTimeout() { + if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT"); + mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT); + // Compute when the next parole needs to happen. We check more frequently than necessary + // since the message handler delays are based on elapsedRealTime and not wallclock time. + // The comparison is done in wallclock time. + long timeLeft = (mLastAppIdleParoledTime + DEFAULT_PAROLE_INTERVAL) + - checkAndGetTimeLocked(); + if (timeLeft < 0) { + timeLeft = 0; + } + mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft / 10); + } + + private void postParoleEndTimeout() { + if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT"); + mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, DEFAULT_PAROLE_DURATION); + } + void postCheckIdleStates() { mHandler.removeMessages(MSG_CHECK_IDLE_STATES); mHandler.sendEmptyMessage(MSG_CHECK_IDLE_STATES); @@ -313,6 +358,24 @@ public class UsageStatsService extends SystemService implements mHandler.sendEmptyMessageDelayed(MSG_CHECK_IDLE_STATES, mCheckIdleIntervalMillis); } + /** Check if it's been a while since last parole and let idle apps do some work */ + void checkParoleTimeout() { + synchronized (mLock) { + if (!mAppIdleParoled) { + final long timeSinceLastParole = checkAndGetTimeLocked() - mLastAppIdleParoledTime; + if (timeSinceLastParole > DEFAULT_PAROLE_INTERVAL) { + if (DEBUG) Slog.d(TAG, "Crossed default parole interval"); + setAppIdleParoled(true); + // Make sure it ends at some point + postParoleEndTimeout(); + } else { + if (DEBUG) Slog.d(TAG, "Not long enough to go to parole"); + postNextParoleTimeout(); + } + } + } + } + void updateDisplayLocked() { boolean screenOn = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState() != Display.STATE_OFF; @@ -368,6 +431,23 @@ public class UsageStatsService extends SystemService implements } } + void onDeviceIdleModeChanged() { + final boolean deviceIdle = mPowerManager.isDeviceIdleMode(); + if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle); + synchronized (mLock) { + final long timeSinceLastParole = checkAndGetTimeLocked() - mLastAppIdleParoledTime; + if (!deviceIdle + && timeSinceLastParole >= DEFAULT_PAROLE_INTERVAL) { + if (DEBUG) Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false"); + postNextParoleTimeout(); + setAppIdleParoled(true); + } else if (deviceIdle) { + if (DEBUG) Slog.i(TAG, "Device idle, back to prison"); + setAppIdleParoled(false); + } + } + } + private static void deleteRecursively(File f) { File[] files = f.listFiles(); if (files != null) { @@ -400,12 +480,20 @@ public class UsageStatsService extends SystemService implements final long actualSystemTime = System.currentTimeMillis(); final long actualRealtime = SystemClock.elapsedRealtime(); final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot; + boolean resetBeginIdleTime = false; if (Math.abs(actualSystemTime - expectedSystemTime) > TIME_CHANGE_THRESHOLD_MILLIS) { // The time has changed. + + // Check if it's severe enough a change to reset screenOnTime + if (Math.abs(actualSystemTime - expectedSystemTime) > mAppIdleDurationMillis) { + mScreenOnSystemTimeSnapshot = actualSystemTime; + mScreenOnTime = 0; + resetBeginIdleTime = true; + } final int userCount = mUserState.size(); for (int i = 0; i < userCount; i++) { final UserUsageStatsService service = mUserState.valueAt(i); - service.onTimeChanged(expectedSystemTime, actualSystemTime); + service.onTimeChanged(expectedSystemTime, actualSystemTime, resetBeginIdleTime); } mRealTimeSnapshot = actualRealtime; mSystemTimeSnapshot = actualSystemTime; @@ -718,6 +806,16 @@ public class UsageStatsService extends SystemService implements case MSG_CHECK_IDLE_STATES: checkIdleStates(); break; + + case MSG_CHECK_PAROLE_TIMEOUT: + checkParoleTimeout(); + break; + + case MSG_PAROLE_END_TIMEOUT: + if (DEBUG) Slog.d(TAG, "Ending parole"); + setAppIdleParoled(false); + break; + default: super.handleMessage(msg); break; diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index b638c8e19bc7..b7e1c22d0a37 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -104,7 +104,7 @@ class UserUsageStatsService { // By calling loadActiveStats, we will // generate new stats for each bucket. - loadActiveStats(currentTimeMillis, false); + loadActiveStats(currentTimeMillis,/*force=*/ false, /*resetBeginIdleTime=*/ false); } else { // Set up the expiry date to be one day from the latest daily stat. // This may actually be today and we will rollover on the first event @@ -167,10 +167,10 @@ class UserUsageStatsService { persistActiveStats(); } - void onTimeChanged(long oldTime, long newTime) { + void onTimeChanged(long oldTime, long newTime, boolean resetBeginIdleTime) { persistActiveStats(); mDatabase.onTimeChanged(newTime - oldTime); - loadActiveStats(newTime, true); + loadActiveStats(newTime, /* force= */ true, resetBeginIdleTime); } void reportEvent(UsageEvents.Event event, long deviceUsageTime) { @@ -431,7 +431,7 @@ class UserUsageStatsService { persistActiveStats(); mDatabase.prune(currentTimeMillis); - loadActiveStats(currentTimeMillis, false); + loadActiveStats(currentTimeMillis, /*force=*/ false, /*resetBeginIdleTime=*/ false); final int continueCount = continuePreviousDay.size(); for (int i = 0; i < continueCount; i++) { @@ -460,7 +460,8 @@ class UserUsageStatsService { /** * @param force To force all in-memory stats to be reloaded. */ - private void loadActiveStats(final long currentTimeMillis, boolean force) { + private void loadActiveStats(final long currentTimeMillis, boolean force, + boolean resetBeginIdleTime) { final UnixCalendar tempCal = mDailyExpiryDate; for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { tempCal.setTimeInMillis(currentTimeMillis); @@ -496,6 +497,12 @@ class UserUsageStatsService { mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis(); mCurrentStats[intervalType].endTime = currentTimeMillis; } + + if (resetBeginIdleTime) { + for (UsageStats usageStats : mCurrentStats[intervalType].packageStats.values()) { + usageStats.mBeginIdleTime = 0; + } + } } mStatsChanged = false; mDailyExpiryDate.setTimeInMillis(currentTimeMillis); -- 2.11.0