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;
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 =
* 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.
* 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.
*/
* 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;
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;
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;
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;
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;
AppWidgetManager mAppWidgetManager;
IDeviceIdleController mDeviceIdleController;
private DisplayManager mDisplayManager;
+ private PowerManager mPowerManager;
private final SparseArray<UserUsageStatsService> 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;
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();
ServiceManager.getService(DeviceIdleController.SERVICE_NAME));
mDisplayManager = (DisplayManager) getContext().getSystemService(
Context.DISPLAY_SERVICE);
+ mPowerManager = getContext().getSystemService(PowerManager.class);
+
mScreenOnSystemTimeSnapshot = System.currentTimeMillis();
synchronized (this) {
mScreenOnTime = readScreenOnTimeLocked();
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();
}
}
}
}
}
+ /** 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);
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;
}
}
+ 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) {
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;
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;
// 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
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) {
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++) {
/**
* @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);
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);