From ef5cf2b4d11f841d3f836b9f536b2e7562ee118b Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Wed, 28 Jun 2017 15:46:03 +0200 Subject: [PATCH 1/2] Factor out AlarmTimeout Factors out a helper class that allows scheduling timeouts that are delivered through alarm manager and cause a wakeup. Test: check that AOD time still updates every minute Bug: 62292293 Change-Id: Iff84c01982f0757095bc8719d41bbd22dab4e329 --- .../src/com/android/systemui/doze/DozeUi.java | 25 ++--- .../android/systemui/util/AlarmTimeout.java | 93 +++++++++++++++++++ 2 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/util/AlarmTimeout.java diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java index cf87fca56f392..97731cf24774f 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) { @@ -112,25 +110,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 +147,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 +154,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(); + } +} From 6023ccbea1568d8b22db82cac9bf7a03367d0a61 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Wed, 28 Jun 2017 16:22:02 +0200 Subject: [PATCH 2/2] AOD: Add hysteresis to pausing the display Delays pausing the display by several seconds to avoid flickering when the prox is only covered transiently. Also adds cooldown to the proximity sensor, such that when it toggles too often it gets disabled for a while. Bug: 62292293 Test: cover prox for less than 10s, observe display stays on. Cover for more, observe display turns off. Change-Id: Ifa407b84760fc299fbbcfa92d9e942e0093c4b73 --- .../android/systemui/doze/DozeFactory.java | 11 ++-- .../android/systemui/doze/DozeMachine.java | 9 +++- .../com/android/systemui/doze/DozePauser.java | 53 +++++++++++++++++++ .../android/systemui/doze/DozeSensors.java | 49 +++++++++++++++-- .../android/systemui/doze/DozeTriggers.java | 20 ++++--- .../src/com/android/systemui/doze/DozeUi.java | 1 + .../systemui/doze/DozeTriggersTest.java | 5 +- 7 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/doze/DozePauser.java 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 97731cf24774f..1dc37cdbca7ad 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java @@ -74,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: 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); }); }