diff options
-rw-r--r-- | services/core/java/com/android/server/AnyMotionDetector.java | 438 | ||||
-rw-r--r-- | services/core/java/com/android/server/DeviceIdleController.java | 115 |
2 files changed, 544 insertions, 9 deletions
diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java new file mode 100644 index 0000000..6390bcd --- /dev/null +++ b/services/core/java/com/android/server/AnyMotionDetector.java @@ -0,0 +1,438 @@ +/* + * 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; + +import android.app.AlarmManager; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.SystemClock; +import android.util.Slog; + +import java.lang.Float; + +/** + * Determines if the device has been set upon a stationary object. + */ +public class AnyMotionDetector { + interface DeviceIdleCallback { + public void onAnyMotionResult(int result); + } + + private static final String TAG = "AnyMotionDetector"; + + private static final boolean DEBUG = false; + + /** Stationary status is unknown due to insufficient orientation measurements. */ + public static final int RESULT_UNKNOWN = -1; + + /** Device is stationary, e.g. still on a table. */ + public static final int RESULT_STATIONARY = 0; + + /** Device has been moved. */ + public static final int RESULT_MOVED = 1; + + /** Orientation measurements are being performed or are planned. */ + private static final int STATE_INACTIVE = 0; + + /** No orientation measurements are being performed or are planned. */ + private static final int STATE_ACTIVE = 1; + + /** Current measurement state. */ + private int mState; + + /** Threshold angle in degrees beyond which the device is considered moving. */ + private final float THRESHOLD_ANGLE = 2f; + + /** Threshold energy above which the device is considered moving. */ + private final float THRESHOLD_ENERGY = 5f; + + /** The duration of the accelerometer orientation measurement. */ + private static final long ORIENTATION_MEASUREMENT_DURATION_MILLIS = 2500; + + /** The maximum duration we will collect accelerometer data. */ + private static final long ACCELEROMETER_DATA_TIMEOUT_MILLIS = 3000; + + /** The interval between accelerometer orientation measurements. */ + private static final long ORIENTATION_MEASUREMENT_INTERVAL_MILLIS = 5000; + + /** + * The duration in milliseconds after which an orientation measurement is considered + * too stale to be used. + */ + private static final int STALE_MEASUREMENT_TIMEOUT_MILLIS = 2 * 60 * 1000; + + /** The accelerometer sampling interval. */ + private static final int SAMPLING_INTERVAL_MILLIS = 40; + + private AlarmManager mAlarmManager; + private final Handler mHandler; + private Intent mAlarmIntent; + private final Object mLock = new Object(); + private Sensor mAccelSensor; + private SensorManager mSensorManager; + private PowerManager.WakeLock mWakeLock; + + /** The time when detection was last performed. */ + private long mDetectionStartTime; + + /** The minimum number of samples required to detect AnyMotion. */ + private int mNumSufficientSamples; + + /** True if an orientation measurement is in progress. */ + private boolean mMeasurementInProgress; + + /** The most recent gravity vector. */ + private Vector3 mCurrentGravityVector = null; + + /** The second most recent gravity vector. */ + private Vector3 mPreviousGravityVector = null; + + /** Running sum of squared errors. */ + private RunningSignalStats mRunningStats; + + private DeviceIdleCallback mCallback = null; + + public AnyMotionDetector(AlarmManager am, PowerManager pm, Handler handler, SensorManager sm, + DeviceIdleCallback callback) { + if (DEBUG) Slog.d(TAG, "AnyMotionDetector instantiated."); + mAlarmManager = am; + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mHandler = handler; + mSensorManager = sm; + mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + mMeasurementInProgress = false; + mState = STATE_INACTIVE; + mCallback = callback; + mRunningStats = new RunningSignalStats(); + mNumSufficientSamples = (int) Math.ceil( + ((double)ORIENTATION_MEASUREMENT_DURATION_MILLIS / SAMPLING_INTERVAL_MILLIS)); + if (DEBUG) Slog.d(TAG, "mNumSufficientSamples = " + mNumSufficientSamples); + } + + /* + * Acquire accel data until we determine AnyMotion status. + */ + public void checkForAnyMotion() { + if (DEBUG) Slog.d(TAG, "checkForAnyMotion(). mState = " + mState); + if (mState != STATE_ACTIVE) { + mState = STATE_ACTIVE; + if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_ACTIVE."); + mCurrentGravityVector = null; + mPreviousGravityVector = null; + startOrientationMeasurement(); + } + } + + private void startOrientationMeasurement() { + if (DEBUG) Slog.d(TAG, "startOrientationMeasurement: mMeasurementInProgress=" + + mMeasurementInProgress + ", (mAccelSensor != null)=" + (mAccelSensor != null)); + + if (!mMeasurementInProgress && mAccelSensor != null) { + if (mSensorManager.registerListener(mListener, mAccelSensor, + SAMPLING_INTERVAL_MILLIS * 1000)) { + mWakeLock.acquire(); + mMeasurementInProgress = true; + mDetectionStartTime = SystemClock.elapsedRealtime(); + mRunningStats.reset(); + } + + Message msg = Message.obtain(mHandler, mMeasurementTimeout); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, ACCELEROMETER_DATA_TIMEOUT_MILLIS); + } + } + + private int stopOrientationMeasurementLocked() { + if (DEBUG) Slog.d(TAG, "stopOrientationMeasurement. mMeasurementInProgress=" + + mMeasurementInProgress); + int status = RESULT_UNKNOWN; + if (mMeasurementInProgress) { + mSensorManager.unregisterListener(mListener); + mHandler.removeCallbacks(mMeasurementTimeout); + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } + long detectionEndTime = SystemClock.elapsedRealtime(); + mMeasurementInProgress = false; + mPreviousGravityVector = mCurrentGravityVector; + mCurrentGravityVector = mRunningStats.getRunningAverage(); + if (DEBUG) { + Slog.d(TAG, "mRunningStats = " + mRunningStats.toString()); + String currentGravityVectorString = (mCurrentGravityVector == null) ? + "null" : mCurrentGravityVector.toString(); + String previousGravityVectorString = (mPreviousGravityVector == null) ? + "null" : mPreviousGravityVector.toString(); + Slog.d(TAG, "mCurrentGravityVector = " + currentGravityVectorString); + Slog.d(TAG, "mPreviousGravityVector = " + previousGravityVectorString); + } + mRunningStats.reset(); + status = getStationaryStatus(); + if (DEBUG) Slog.d(TAG, "getStationaryStatus() returned " + status); + if (status != RESULT_UNKNOWN) { + if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE. status = " + + status); + mState = STATE_INACTIVE; + } else { + /* + * Unknown due to insufficient measurements. Schedule another orientation + * measurement. + */ + if (DEBUG) Slog.d(TAG, "stopOrientationMeasurementLocked(): another measurement" + + " scheduled in " + ORIENTATION_MEASUREMENT_INTERVAL_MILLIS + + " milliseconds."); + Message msg = Message.obtain(mHandler, mSensorRestart); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, ORIENTATION_MEASUREMENT_INTERVAL_MILLIS); + } + } + return status; + } + + /* + * Updates mStatus to the current AnyMotion status. + */ + public int getStationaryStatus() { + if ((mPreviousGravityVector == null) || (mCurrentGravityVector == null)) { + return RESULT_UNKNOWN; + } + Vector3 previousGravityVectorNormalized = mPreviousGravityVector.normalized(); + Vector3 currentGravityVectorNormalized = mCurrentGravityVector.normalized(); + float angle = previousGravityVectorNormalized.angleBetween(currentGravityVectorNormalized); + if (DEBUG) Slog.d(TAG, "getStationaryStatus: angle = " + angle); + if ((angle < THRESHOLD_ANGLE) && (mRunningStats.getEnergy() < THRESHOLD_ENERGY)) { + return RESULT_STATIONARY; + } else if (Float.isNaN(angle)) { + /** + * Floating point rounding errors have caused the angle calcuation's dot product to + * exceed 1.0. In such case, we report RESULT_MOVED to prevent devices from rapidly + * retrying this measurement. + */ + return RESULT_MOVED; + } + long diffTime = mCurrentGravityVector.timeMillisSinceBoot - + mPreviousGravityVector.timeMillisSinceBoot; + if (diffTime > STALE_MEASUREMENT_TIMEOUT_MILLIS) { + if (DEBUG) Slog.d(TAG, "getStationaryStatus: mPreviousGravityVector is too stale at " + + diffTime + " ms ago. Returning RESULT_UNKNOWN."); + return RESULT_UNKNOWN; + } + return RESULT_MOVED; + } + + private final SensorEventListener mListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + int status = RESULT_UNKNOWN; + synchronized (mLock) { + Vector3 accelDatum = new Vector3(SystemClock.elapsedRealtime(), event.values[0], + event.values[1], event.values[2]); + mRunningStats.accumulate(accelDatum); + + // If we have enough samples, stop accelerometer data acquisition. + if (mRunningStats.getSampleCount() >= mNumSufficientSamples) { + status = stopOrientationMeasurementLocked(); + } + } + if (status != RESULT_UNKNOWN) { + mCallback.onAnyMotionResult(status); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + + private final Runnable mSensorRestart = new Runnable() { + @Override + public void run() { + synchronized (mLock) { + startOrientationMeasurement(); + } + } + }; + + private final Runnable mMeasurementTimeout = new Runnable() { + @Override + public void run() { + int status = RESULT_UNKNOWN; + synchronized (mLock) { + if (DEBUG) Slog.i(TAG, "mMeasurementTimeout. Failed to collect sufficient accel " + + "data within " + ACCELEROMETER_DATA_TIMEOUT_MILLIS + " ms. Stopping " + + "orientation measurement."); + status = stopOrientationMeasurementLocked(); + } + if (status != RESULT_UNKNOWN) { + mCallback.onAnyMotionResult(status); + } + } + }; + + /** + * A timestamped three dimensional vector and some vector operations. + */ + private static class Vector3 { + public long timeMillisSinceBoot; + public float x; + public float y; + public float z; + + public Vector3(long timeMillisSinceBoot, float x, float y, float z) { + this.timeMillisSinceBoot = timeMillisSinceBoot; + this.x = x; + this.y = y; + this.z = z; + } + + private float norm() { + return (float) Math.sqrt(dotProduct(this)); + } + + private Vector3 normalized() { + float mag = norm(); + return new Vector3(timeMillisSinceBoot, x / mag, y / mag, z / mag); + } + + /** + * Returns the angle between this 3D vector and another given 3D vector. + * Assumes both have already been normalized. + * + * @param other The other Vector3 vector. + * @return angle between this vector and the other given one. + */ + public float angleBetween(Vector3 other) { + double degrees = Math.toDegrees(Math.acos(this.dotProduct(other))); + float returnValue = (float) degrees; + Slog.d(TAG, "angleBetween: this = " + this.toString() + + ", other = " + other.toString()); + Slog.d(TAG, " degrees = " + degrees + ", returnValue = " + returnValue); + return returnValue; + } + + @Override + public String toString() { + String msg = ""; + msg += "timeMillisSinceBoot=" + timeMillisSinceBoot; + msg += " | x=" + x; + msg += ", y=" + y; + msg += ", z=" + z; + return msg; + } + + public float dotProduct(Vector3 v) { + return x * v.x + y * v.y + z * v.z; + } + + public Vector3 times(float val) { + return new Vector3(timeMillisSinceBoot, x * val, y * val, z * val); + } + + public Vector3 plus(Vector3 v) { + return new Vector3(v.timeMillisSinceBoot, x + v.x, y + v.y, z + v.z); + } + + public Vector3 minus(Vector3 v) { + return new Vector3(v.timeMillisSinceBoot, x - v.x, y - v.y, z - v.z); + } + } + + /** + * Maintains running statistics on the signal revelant to AnyMotion detection, including: + * <ul> + * <li>running average. + * <li>running sum-of-squared-errors as the energy of the signal derivative. + * <ul> + */ + private static class RunningSignalStats { + Vector3 previousVector; + Vector3 currentVector; + Vector3 runningSum; + float energy; + int sampleCount; + + public RunningSignalStats() { + reset(); + } + + public void reset() { + previousVector = null; + currentVector = null; + runningSum = new Vector3(0, 0, 0, 0); + energy = 0; + sampleCount = 0; + } + + /** + * Apply a 3D vector v as the next element in the running SSE. + */ + public void accumulate(Vector3 v) { + if (v == null) { + if (DEBUG) Slog.i(TAG, "Cannot accumulate a null vector."); + return; + } + sampleCount++; + runningSum = runningSum.plus(v); + previousVector = currentVector; + currentVector = v; + if (previousVector != null) { + Vector3 dv = currentVector.minus(previousVector); + float incrementalEnergy = dv.x * dv.x + dv.y * dv.y + dv.z * dv.z; + energy += incrementalEnergy; + if (DEBUG) Slog.i(TAG, "Accumulated vector " + currentVector.toString() + + ", runningSum = " + runningSum.toString() + + ", incrementalEnergy = " + incrementalEnergy + + ", energy = " + energy); + } + } + + public Vector3 getRunningAverage() { + if (sampleCount > 0) { + return runningSum.times((float)(1.0f / sampleCount)); + } + return null; + } + + public float getEnergy() { + return energy; + } + + public int getSampleCount() { + return sampleCount; + } + + @Override + public String toString() { + String msg = ""; + String currentVectorString = (currentVector == null) ? + "null" : currentVector.toString(); + String previousVectorString = (previousVector == null) ? + "null" : previousVector.toString(); + msg += "previousVector = " + previousVectorString; + msg += ", currentVector = " + currentVectorString; + msg += ", sampleCount = " + sampleCount; + msg += ", energy = " + energy; + return msg; + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index e9759c3..4c7b523 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -85,10 +85,12 @@ import java.util.Arrays; /** * Keeps track of device idleness and drives low power mode based on that. */ -public class DeviceIdleController extends SystemService { +public class DeviceIdleController extends SystemService + implements AnyMotionDetector.DeviceIdleCallback { private static final String TAG = "DeviceIdleController"; private static final boolean DEBUG = false; + private static final boolean COMPRESS_TIME = false; public static final String SERVICE_NAME = "deviceidle"; @@ -96,6 +98,9 @@ public class DeviceIdleController extends SystemService { private static final String ACTION_STEP_IDLE_STATE = "com.android.server.device_idle.STEP_IDLE_STATE"; + private static final String ACTION_ENTER_INACTIVE_STATE = + "com.android.server.device_idle.ENTER_INACTIVE_STATE"; + // TODO: These need to be moved to system settings. /** @@ -104,26 +109,40 @@ 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 = !COMPRESS_TIME ? 30*60*1000L : 3 * 60 * 1000L; + + /** + * If we don't receive a callback from AnyMotion in this amount of time, we will change from + * STATE_SENSING to STATE_INACTIVE, and any AnyMotion callbacks while not in STATE_SENSING will + * be ignored. + */ + private static final long DEFAULT_SENSING_TIMEOUT = !DEBUG ? 5 * 60 * 1000L : 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 = !COMPRESS_TIME ? 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 = !COMPRESS_TIME ? 30*60*1000L : 3 * 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. */ + private static final long DEFAULT_IDLE_PENDING_TIMEOUT = !COMPRESS_TIME ? 5*60*1000L : 30 * 1000L; + /** * Maximum pending idle timeout (time spent running) we will be allowed to use. */ @@ -138,8 +157,10 @@ 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 = !COMPRESS_TIME ? 60*60*1000L : 6 * 60 * 1000L; + /** * Maximum idle duration we will be allowed to use. */ @@ -168,9 +189,11 @@ public class DeviceIdleController extends SystemService { private DisplayManager mDisplayManager; private SensorManager mSensorManager; private Sensor mSigMotionSensor; + private PendingIntent mSensingAlarmIntent; private PendingIntent mAlarmIntent; private Intent mIdleIntent; private Display mCurDisplay; + private AnyMotionDetector mAnyMotionDetector; private boolean mIdleDisabled; private boolean mScreenOn; private boolean mCharging; @@ -182,15 +205,18 @@ public class DeviceIdleController extends SystemService { private static final int STATE_INACTIVE = 1; /** Device is past the initial inactive period, and waiting for the next idle period. */ private static final int STATE_IDLE_PENDING = 2; + /** Device is currently sensing motion. */ + private static final int STATE_SENSING = 3; /** Device is in the idle state, trying to stay asleep as much as possible. */ - private static final int STATE_IDLE = 3; + private static final int STATE_IDLE = 4; /** Device is in the idle state, but temporarily out of idle to do regular maintenance. */ - private static final int STATE_IDLE_MAINTENANCE = 4; + private static final int STATE_IDLE_MAINTENANCE = 5; private static String stateToString(int state) { switch (state) { case STATE_ACTIVE: return "ACTIVE"; case STATE_INACTIVE: return "INACTIVE"; case STATE_IDLE_PENDING: return "IDLE_PENDING"; + case STATE_SENSING: return "SENSING"; case STATE_IDLE: return "IDLE"; case STATE_IDLE_MAINTENANCE: return "IDLE_MAINTENANCE"; default: return Integer.toString(state); @@ -247,6 +273,10 @@ public class DeviceIdleController extends SystemService { synchronized (DeviceIdleController.this) { stepIdleStateLocked(); } + } else if (ACTION_ENTER_INACTIVE_STATE.equals(intent.getAction())) { + synchronized (DeviceIdleController.this) { + enterInactiveStateLocked(); + } } } }; @@ -276,6 +306,24 @@ public class DeviceIdleController extends SystemService { } }; + @Override + public void onAnyMotionResult(int result) { + if (DEBUG) Slog.d(TAG, "onAnyMotionResult(" + result + ")"); + if (mState == STATE_SENSING) { + if (result == AnyMotionDetector.RESULT_STATIONARY) { + if (DEBUG) Slog.d(TAG, "RESULT_STATIONARY received."); + synchronized (this) { + stepIdleStateLocked(); + } + } else if (result == AnyMotionDetector.RESULT_MOVED) { + if (DEBUG) Slog.d(TAG, "RESULT_MOVED received."); + synchronized (this) { + enterInactiveStateLocked(); + } + } + } + } + static final int MSG_WRITE_CONFIG = 1; static final int MSG_REPORT_IDLE_ON = 2; static final int MSG_REPORT_IDLE_OFF = 3; @@ -288,6 +336,7 @@ public class DeviceIdleController extends SystemService { } @Override public void handleMessage(Message msg) { + if (DEBUG) Slog.d(TAG, "handleMessage(" + msg.what + ")"); switch (msg.what) { case MSG_WRITE_CONFIG: { handleWriteConfigFile(); @@ -452,12 +501,21 @@ public class DeviceIdleController extends SystemService { Context.DISPLAY_SERVICE); mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE); mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION); + mAnyMotionDetector = new AnyMotionDetector( + mAlarmManager, + (PowerManager) getContext().getSystemService(Context.POWER_SERVICE), + mHandler, mSensorManager, this); Intent intent = new Intent(ACTION_STEP_IDLE_STATE) .setPackage("android") .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0); + Intent intentSensing = new Intent(ACTION_STEP_IDLE_STATE) + .setPackage("android") + .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mSensingAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intentSensing, 0); + mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); @@ -613,6 +671,7 @@ public class DeviceIdleController extends SystemService { // because if there is anything shown we are going to be updating it at some // frequency so can't be allowed to go into deep sleeps. boolean screenOn = mCurDisplay.getState() != Display.STATE_OFF;; + if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn); if (!screenOn && mScreenOn) { mScreenOn = false; becomeInactiveIfAppropriateLocked(); @@ -623,6 +682,7 @@ public class DeviceIdleController extends SystemService { } void updateChargingLocked(boolean charging) { + if (DEBUG) Slog.i(TAG, "updateChargingLocked: charging=" + charging); if (!charging && mCharging) { mCharging = false; becomeInactiveIfAppropriateLocked(); @@ -639,6 +699,7 @@ public class DeviceIdleController extends SystemService { } void becomeActiveLocked(String reason) { + if (DEBUG) Slog.i(TAG, "becomeActiveLocked, reason = " + reason); if (mState != STATE_ACTIVE) { EventLogTags.writeDeviceIdle(STATE_ACTIVE, reason); scheduleReportActiveLocked(false); @@ -652,10 +713,12 @@ public class DeviceIdleController extends SystemService { } void becomeInactiveIfAppropriateLocked() { + if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()"); if (!mScreenOn && !mCharging && !mIdleDisabled && mState == STATE_ACTIVE) { // Screen has turned off; we are now going to become inactive and start // waiting to see if we will ultimately go idle. mState = STATE_INACTIVE; + if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE"); mNextIdlePendingDelay = 0; mNextIdleDelay = 0; scheduleAlarmLocked(mInactiveTimeout, false); @@ -663,7 +726,17 @@ public class DeviceIdleController extends SystemService { } } + /** + * This is called when we've failed to receive a callback from AnyMotionDetector + * within the DEFAULT_SENSING_TIMEOUT, to return to STATE_INACTIVE. + */ + void enterInactiveStateLocked() { + mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT; + becomeInactiveIfAppropriateLocked(); + } + void stepIdleStateLocked() { + if (DEBUG) Slog.d(TAG, "stepIdleStateLocked: mState=" + mState); EventLogTags.writeDeviceIdleStep(); final long now = SystemClock.elapsedRealtime(); @@ -685,26 +758,34 @@ public class DeviceIdleController extends SystemService { mNextIdlePendingDelay = DEFAULT_IDLE_PENDING_TIMEOUT; mNextIdleDelay = DEFAULT_IDLE_TIMEOUT; mState = STATE_IDLE_PENDING; + if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_IDLE_PENDING."); EventLogTags.writeDeviceIdle(mState, "step"); break; case STATE_IDLE_PENDING: + mState = STATE_SENSING; + if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING."); + scheduleSensingAlarmLocked(DEFAULT_SENSING_TIMEOUT); + mAnyMotionDetector.checkForAnyMotion(); + break; + case STATE_SENSING: + cancelSensingAlarmLocked(); case STATE_IDLE_MAINTENANCE: - // We have been waiting to become idle, and now it is time! This is the - // only case where we want to use a wakeup alarm, because we do want to - // drag the device out of its sleep state in this case to do the next - // scheduled work. scheduleAlarmLocked(mNextIdleDelay, true); - mNextIdleDelay = (long)(mNextIdleDelay*DEFAULT_IDLE_FACTOR); + if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay + + " ms."); + mNextIdleDelay = (long)(mNextIdleDelay * DEFAULT_IDLE_FACTOR); + if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay); if (mNextIdleDelay > DEFAULT_MAX_IDLE_TIMEOUT) { mNextIdleDelay = DEFAULT_MAX_IDLE_TIMEOUT; } mState = STATE_IDLE; - EventLogTags.writeDeviceIdle(mState, "step"); mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON); break; case STATE_IDLE: // We have been idling long enough, now it is time to do some work. scheduleAlarmLocked(mNextIdlePendingDelay, false); + if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " + + "Next alarm in " + mNextIdlePendingDelay + " ms."); mNextIdlePendingDelay = (long)(mNextIdlePendingDelay*DEFAULT_IDLE_PENDING_FACTOR); if (mNextIdlePendingDelay > DEFAULT_MAX_IDLE_PENDING_TIMEOUT) { mNextIdlePendingDelay = DEFAULT_MAX_IDLE_PENDING_TIMEOUT; @@ -717,6 +798,7 @@ public class DeviceIdleController extends SystemService { } void significantMotionLocked() { + if (DEBUG) Slog.d(TAG, "significantMotionLocked()"); // When the sensor goes off, its trigger is automatically removed. mSigMotionActive = false; // The device is not yet active, so we want to go back to the pending idle @@ -732,6 +814,7 @@ public class DeviceIdleController extends SystemService { } void startMonitoringSignificantMotion() { + if (DEBUG) Slog.d(TAG, "startMonitoringSignificantMotion()"); if (mSigMotionSensor != null && !mSigMotionActive) { mSensorManager.requestTriggerSensor(mSigMotionListener, mSigMotionSensor); mSigMotionActive = true; @@ -739,6 +822,7 @@ public class DeviceIdleController extends SystemService { } void stopMonitoringSignificantMotion() { + if (DEBUG) Slog.d(TAG, "stopMonitoringSignificantMotion()"); if (mSigMotionActive) { mSensorManager.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor); mSigMotionActive = false; @@ -752,7 +836,13 @@ public class DeviceIdleController extends SystemService { } } + void cancelSensingAlarmLocked() { + if (DEBUG) Slog.d(TAG, "cancelSensingAlarmLocked()"); + mAlarmManager.cancel(mSensingAlarmIntent); + } + void scheduleAlarmLocked(long delay, boolean idleUntil) { + if (DEBUG) Slog.d(TAG, "scheduleAlarmLocked(" + delay + ", " + idleUntil + ")"); if (mSigMotionSensor == null) { // If there is no significant motion sensor on this device, then we won't schedule // alarms, because we can't determine if the device is not moving. This effectively @@ -770,6 +860,13 @@ public class DeviceIdleController extends SystemService { } } + void scheduleSensingAlarmLocked(long delay) { + if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")"); + mNextAlarmTime = SystemClock.elapsedRealtime() + delay; + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + mNextAlarmTime, mSensingAlarmIntent); + } + private void updateWhitelistAppIdsLocked() { mPowerSaveWhitelistAppIds.clear(); for (int i=0; i<mPowerSaveWhitelistApps.size(); i++) { |