diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java index 1cc10c22082ad..c072772552544 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java @@ -52,8 +52,9 @@ public class DozeFactory { config, wakeLock); machine.setParts(new DozeMachine.Part[]{ - createDozeTriggers(context, sensorManager, host, config, params, handler, wakeLock, - machine), + new DozePauser(handler, machine, alarmManager), + createDozeTriggers(context, sensorManager, host, alarmManager, config, params, + handler, wakeLock, machine), createDozeUi(context, host, wakeLock, machine, handler, alarmManager), }); @@ -61,10 +62,10 @@ public class DozeFactory { } private DozeTriggers createDozeTriggers(Context context, SensorManager sensorManager, - DozeHost host, AmbientDisplayConfiguration config, DozeParameters params, - Handler handler, WakeLock wakeLock, DozeMachine machine) { + DozeHost host, AlarmManager alarmManager, AmbientDisplayConfiguration config, + DozeParameters params, Handler handler, WakeLock wakeLock, DozeMachine machine) { boolean allowPulseTriggers = true; - return new DozeTriggers(context, machine, host, config, params, + return new DozeTriggers(context, machine, host, alarmManager, config, params, sensorManager, handler, wakeLock, allowPulseTriggers); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index 5526e6ba3f454..348dd97ab96a3 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -60,13 +60,16 @@ public class DozeMachine { /** Doze is done. DozeService is finished. */ FINISH, /** AOD, but the display is temporarily off. */ - DOZE_AOD_PAUSED; + DOZE_AOD_PAUSED, + /** AOD, prox is near, transitions to DOZE_AOD_PAUSED after a timeout. */ + DOZE_AOD_PAUSING; boolean canPulse() { switch (this) { case DOZE: case DOZE_AOD: case DOZE_AOD_PAUSED: + case DOZE_AOD_PAUSING: return true; default: return false; @@ -93,6 +96,7 @@ public class DozeMachine { case DOZE_PULSING: return Display.STATE_ON; case DOZE_AOD: + case DOZE_AOD_PAUSING: return Display.STATE_DOZE_SUSPEND; default: return Display.STATE_UNKNOWN; @@ -284,7 +288,8 @@ public class DozeMachine { if (mState == State.FINISH) { return State.FINISH; } - if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD || mState == State.DOZE) + if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING + || mState == State.DOZE_AOD || mState == State.DOZE) && requestedState == State.DOZE_PULSE_DONE) { Log.i(TAG, "Dropping pulse done because current state is already done: " + mState); return mState; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java new file mode 100644 index 0000000000000..a33b454c6430e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/doze/DozePauser.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 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.systemui.doze; + +import android.app.AlarmManager; +import android.os.Handler; + +import com.android.systemui.util.AlarmTimeout; + +/** + * Moves the doze machine from the pausing to the paused state after a timeout. + */ +public class DozePauser implements DozeMachine.Part { + public static final String TAG = DozePauser.class.getSimpleName(); + private static final long TIMEOUT = 10 * 1000; + private final AlarmTimeout mPauseTimeout; + private final DozeMachine mMachine; + + public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager) { + mMachine = machine; + mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler); + } + + @Override + public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { + switch (newState) { + case DOZE_AOD_PAUSING: + mPauseTimeout.schedule(TIMEOUT, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); + break; + default: + mPauseTimeout.cancel(); + break; + } + } + + private void onTimeout() { + mMachine.requestState(DozeMachine.State.DOZE_AOD_PAUSED); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 23da716706d3c..df840ea20383a 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -18,6 +18,7 @@ package com.android.systemui.doze; import android.annotation.AnyThread; import android.app.ActivityManager; +import android.app.AlarmManager; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -29,6 +30,7 @@ import android.hardware.TriggerEvent; import android.hardware.TriggerEventListener; import android.net.Uri; import android.os.Handler; +import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -38,6 +40,7 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; @@ -51,6 +54,7 @@ public class DozeSensors { private static final String TAG = "DozeSensors"; private final Context mContext; + private final AlarmManager mAlarmManager; private final SensorManager mSensorManager; private final TriggerSensor[] mSensors; private final ContentResolver mResolver; @@ -65,10 +69,12 @@ public class DozeSensors { private final ProxSensor mProxSensor; - public DozeSensors(Context context, SensorManager sensorManager, DozeParameters dozeParameters, + public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager, + DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, Consumer proxCallback) { mContext = context; + mAlarmManager = alarmManager; mSensorManager = sensorManager; mDozeParameters = dozeParameters; mConfig = config; @@ -140,7 +146,7 @@ public class DozeSensors { } public void setProxListening(boolean listen) { - mProxSensor.setRegistered(listen); + mProxSensor.setRequested(listen); } private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { @@ -168,11 +174,23 @@ public class DozeSensors { private class ProxSensor implements SensorEventListener { + static final long COOLDOWN_TRIGGER = 2 * 1000; + static final long COOLDOWN_PERIOD = 5 * 1000; + + boolean mRequested; boolean mRegistered; Boolean mCurrentlyFar; + long mLastNear; + final AlarmTimeout mCooldownTimer; - void setRegistered(boolean register) { - if (mRegistered == register) { + + public ProxSensor() { + mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered, + "prox_cooldown", mHandler); + } + + void setRequested(boolean requested) { + if (mRequested == requested) { // Send an update even if we don't re-register. mHandler.post(() -> { if (mCurrentlyFar != null) { @@ -181,6 +199,18 @@ public class DozeSensors { }); return; } + mRequested = requested; + updateRegistered(); + } + + private void updateRegistered() { + setRegistered(mRequested && !mCooldownTimer.isScheduled()); + } + + private void setRegistered(boolean register) { + if (mRegistered == register) { + return; + } if (register) { mRegistered = mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY), @@ -196,6 +226,17 @@ public class DozeSensors { public void onSensorChanged(SensorEvent event) { mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange(); mProxCallback.accept(mCurrentlyFar); + + long now = SystemClock.elapsedRealtime(); + if (!mCurrentlyFar) { + mLastNear = now; + } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) { + // If the last near was very recent, we might be using more power for prox + // wakeups than we're saving from turning of the screen. Instead, turn it off + // for a while. + mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); + updateRegistered(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 8d1d6e0ce460d..610eaffec34b8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -16,6 +16,7 @@ package com.android.systemui.doze; +import android.app.AlarmManager; import android.app.UiModeManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -70,7 +71,7 @@ public class DozeTriggers implements DozeMachine.Part { public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, - AmbientDisplayConfiguration config, + AlarmManager alarmManager, AmbientDisplayConfiguration config, DozeParameters dozeParameters, SensorManager sensorManager, Handler handler, WakeLock wakeLock, boolean allowPulseTriggers) { mContext = context; @@ -82,8 +83,8 @@ public class DozeTriggers implements DozeMachine.Part { mHandler = handler; mWakeLock = wakeLock; mAllowPulseTriggers = allowPulseTriggers; - mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters, config, - wakeLock, this::onSensor, this::onProximityFar); + mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters, + config, wakeLock, this::onSensor, this::onProximityFar); mUiModeManager = mContext.getSystemService(UiModeManager.class); } @@ -152,18 +153,22 @@ public class DozeTriggers implements DozeMachine.Part { private void onProximityFar(boolean far) { final boolean near = !far; - DozeMachine.State state = mMachine.getState(); + final DozeMachine.State state = mMachine.getState(); + final boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED); + final boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING); + final boolean aod = (state == DozeMachine.State.DOZE_AOD); + if (near && state == DozeMachine.State.DOZE_PULSING) { if (DEBUG) Log.i(TAG, "Prox NEAR, ending pulse"); DozeLog.tracePulseCanceledByProx(mContext); mMachine.requestState(DozeMachine.State.DOZE_PULSE_DONE); } - if (far && state == DozeMachine.State.DOZE_AOD_PAUSED) { + if (far && (paused || pausing)) { if (DEBUG) Log.i(TAG, "Prox FAR, unpausing AOD"); mMachine.requestState(DozeMachine.State.DOZE_AOD); - } else if (near && state == DozeMachine.State.DOZE_AOD) { + } else if (near && aod) { if (DEBUG) Log.i(TAG, "Prox NEAR, pausing AOD"); - mMachine.requestState(DozeMachine.State.DOZE_AOD_PAUSED); + mMachine.requestState(DozeMachine.State.DOZE_AOD_PAUSING); } } @@ -192,6 +197,7 @@ public class DozeTriggers implements DozeMachine.Part { } break; case DOZE_AOD_PAUSED: + case DOZE_AOD_PAUSING: mDozeSensors.setProxListening(true); mDozeSensors.setListening(false); break; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index cf87fca56f392..1dc37cdbca7ad 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -23,6 +23,7 @@ import android.os.SystemClock; import android.text.format.Formatter; import android.util.Log; +import com.android.systemui.util.AlarmTimeout; import com.android.systemui.util.wakelock.WakeLock; import java.util.Calendar; @@ -35,26 +36,23 @@ public class DozeUi implements DozeMachine.Part { private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min private final Context mContext; - private final AlarmManager mAlarmManager; private final DozeHost mHost; private final Handler mHandler; private final WakeLock mWakeLock; private final DozeMachine mMachine; - private final AlarmManager.OnAlarmListener mTimeTick; + private final AlarmTimeout mTimeTicker; - private boolean mTimeTickScheduled = false; private long mLastTimeTickElapsed = 0; public DozeUi(Context context, AlarmManager alarmManager, DozeMachine machine, WakeLock wakeLock, DozeHost host, Handler handler) { mContext = context; - mAlarmManager = alarmManager; mMachine = machine; mWakeLock = wakeLock; mHost = host; mHandler = handler; - mTimeTick = this::onTimeTick; + mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler); } private void pulseWhileDozing(int reason) { @@ -76,6 +74,7 @@ public class DozeUi implements DozeMachine.Part { public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case DOZE_AOD: + case DOZE_AOD_PAUSING: scheduleTimeTick(); break; case DOZE: @@ -112,25 +111,21 @@ public class DozeUi implements DozeMachine.Part { } private void scheduleTimeTick() { - if (mTimeTickScheduled) { + if (mTimeTicker.isScheduled()) { return; } long delta = roundToNextMinute(System.currentTimeMillis()) - System.currentTimeMillis(); - mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + delta, "doze_time_tick", mTimeTick, mHandler); - - mTimeTickScheduled = true; + mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); mLastTimeTickElapsed = SystemClock.elapsedRealtime(); } private void unscheduleTimeTick() { - if (!mTimeTickScheduled) { + if (!mTimeTicker.isScheduled()) { return; } verifyLastTimeTick(); - mAlarmManager.cancel(mTimeTick); - mTimeTickScheduled = false; + mTimeTicker.cancel(); } private void verifyLastTimeTick() { @@ -153,10 +148,6 @@ public class DozeUi implements DozeMachine.Part { } private void onTimeTick() { - if (!mTimeTickScheduled) { - // Alarm was canceled, but we still got the callback. Ignore. - return; - } verifyLastTimeTick(); mHost.dozeTimeTick(); @@ -164,7 +155,6 @@ public class DozeUi implements DozeMachine.Part { // Keep wakelock until a frame has been pushed. mHandler.post(mWakeLock.wrap(() -> {})); - mTimeTickScheduled = false; scheduleTimeTick(); } } diff --git a/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java b/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java new file mode 100644 index 0000000000000..f7f61aff9849c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 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.systemui.util; + +import android.app.AlarmManager; +import android.os.Handler; +import android.os.SystemClock; + +/** + * Schedules a timeout through AlarmManager. Ensures that the timeout is called even when + * the device is asleep. + */ +public class AlarmTimeout implements AlarmManager.OnAlarmListener { + + public static final int MODE_CRASH_IF_SCHEDULED = 0; + public static final int MODE_IGNORE_IF_SCHEDULED = 1; + public static final int MODE_RESCHEDULE_IF_SCHEDULED = 2; + + private final AlarmManager mAlarmManager; + private final AlarmManager.OnAlarmListener mListener; + private final String mTag; + private final Handler mHandler; + private boolean mScheduled; + + public AlarmTimeout(AlarmManager alarmManager, AlarmManager.OnAlarmListener listener, + String tag, Handler handler) { + mAlarmManager = alarmManager; + mListener = listener; + mTag = tag; + mHandler = handler; + } + + public void schedule(long timeout, int mode) { + switch (mode) { + case MODE_CRASH_IF_SCHEDULED: + if (mScheduled) { + throw new IllegalStateException(mTag + " timeout is already scheduled"); + } + break; + case MODE_IGNORE_IF_SCHEDULED: + if (mScheduled) { + return; + } + break; + case MODE_RESCHEDULE_IF_SCHEDULED: + if (mScheduled) { + cancel(); + } + break; + default: + throw new IllegalArgumentException("Illegal mode: " + mode); + } + + mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + timeout, mTag, this, mHandler); + mScheduled = true; + } + + public boolean isScheduled() { + return mScheduled; + } + + public void cancel() { + if (mScheduled) { + mAlarmManager.cancel(this); + mScheduled = false; + } + } + + @Override + public void onAlarm() { + if (!mScheduled) { + // We canceled the alarm, but it still fired. Ignore. + return; + } + mScheduled = false; + mListener.onAlarm(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index 8641faca5d6e2..a8ea1c0770da8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.AlarmManager; import android.app.Instrumentation; import android.os.Handler; import android.os.Looper; @@ -56,6 +57,7 @@ public class DozeTriggersTest extends SysuiTestCase { private Handler mHandler; private WakeLock mWakeLock; private Instrumentation mInstrumentation; + private AlarmManager mAlarmManager; @BeforeClass public static void setupSuite() { @@ -67,6 +69,7 @@ public class DozeTriggersTest extends SysuiTestCase { public void setUp() throws Exception { mInstrumentation = InstrumentationRegistry.getInstrumentation(); mMachine = mock(DozeMachine.class); + mAlarmManager = mock(AlarmManager.class); mHost = new DozeHostFake(); mConfig = DozeConfigurationUtil.createMockConfig(); mParameters = DozeConfigurationUtil.createMockParameters(); @@ -75,7 +78,7 @@ public class DozeTriggersTest extends SysuiTestCase { mWakeLock = new WakeLockFake(); mInstrumentation.runOnMainSync(() -> { - mTriggers = new DozeTriggers(mContext, mMachine, mHost, + mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, mConfig, mParameters, mSensors, mHandler, mWakeLock, true); }); }