Refactor idle/low-light state management.

- remove custom idle timeout, now dreams are triggered by system
inactivity
- remove mDozeToDreamLock, which wakes up the device briefly from
dozing, and puts it to dream. Sync with PowerManager on a better
approach.

Test: atest IdleHostViewControllerTest
Bug: 195606657
Fix: 195606657
Change-Id: Ife31527affa3943d5bce4c5ffad35ec33a397528
This commit is contained in:
Darrell Shi
2021-10-29 17:17:00 +00:00
parent 8e9b12cd16
commit f9edc5c6ff
4 changed files with 25 additions and 379 deletions

View File

@@ -638,9 +638,6 @@
screen on inactivity. -->
<bool name="config_enableIdleMode">false</bool>
<!-- Timeout to idle mode duration in milliseconds. -->
<integer name="config_idleModeTimeout">10000</integer>
<!-- This value is used when calculating whether the device is in ambient light mode. It is
light mode when the light sensor sample value exceeds above this value. -->
<integer name="config_ambientLightModeThreshold">5</integer>

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) 2021 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.idle;
import android.content.Context;
import android.service.dreams.Sandman;
import javax.inject.Inject;
/**
* A helper class to the idle mode for requests related to the
* {@link DreamService}.
*/
public class DreamHelper {
@Inject
protected DreamHelper() {
}
/**
* Requests the system to start dreaming.
*
* @param context The context within which the dream request is sent.
*/
public void startDreaming(Context context) {
Sandman.startDreamByUserRequest(context);
}
}

View File

@@ -24,22 +24,17 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
import android.view.Choreographer;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -52,13 +47,12 @@ import javax.inject.Provider;
* {@link IdleHostViewController} processes signals to control the lifecycle of the idle screen.
*/
public class IdleHostViewController extends ViewController<IdleHostView> {
private static final String INPUT_MONITOR_IDENTIFIER = "IdleHostViewController";
private static final String TAG = "IdleHostViewController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@Retention(RetentionPolicy.RUNTIME)
@IntDef({STATE_IDLE_MODE_ENABLED, STATE_KEYGUARD_SHOWING, STATE_DOZING, STATE_DREAMING,
STATE_LOW_LIGHT, STATE_IDLING, STATE_SHOULD_START_IDLING})
STATE_LOW_LIGHT})
public @interface State {}
// Set at construction to indicate idle mode is available.
@@ -76,76 +70,27 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
// Set when the device is in a low light environment.
private static final int STATE_LOW_LIGHT = 1 << 4;
// Set when the device is idling, which is either dozing or dreaming.
private static final int STATE_IDLING = 1 << 5;
// Set when the controller decides that the device should start idling (either dozing or
// dreaming).
private static final int STATE_SHOULD_START_IDLING = 1 << 6;
// The aggregate current state.
private int mState;
private boolean mIdleModeActive;
private boolean mLowLightModeActive;
private boolean mIsMonitoringLowLight;
private boolean mIsMonitoringDream;
// Whether in a state waiting for dozing to complete before starting dreaming.
private boolean mDozeToDreamLock = false;
private final Context mContext;
// Timeout to idle in milliseconds.
private final int mIdleTimeout;
// Factory for generating input listeners.
private final InputMonitorFactory mInputMonitorFactory;
// Delayable executor.
private final DelayableExecutor mDelayableExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
private final PowerManager mPowerManager;
// Runnable for canceling enabling idle.
private Runnable mCancelEnableIdling;
// Keyguard state controller for monitoring keyguard show state.
private final KeyguardStateController mKeyguardStateController;
// Status bar state controller for monitoring when the device is dozing.
private final StatusBarStateController mStatusBarStateController;
// Looper to use for monitoring input.
private final Looper mLooper;
// Choreographer to use for monitoring input.
private final Choreographer mChoreographer;
// Helper class for DreamService related requests.
private final DreamHelper mDreamHelper;
// Monitor for tracking touches for activity.
private InputMonitorCompat mInputMonitor;
// Input receiver of touch activities.
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
// Intent filter for receiving dream broadcasts.
private IntentFilter mDreamIntentFilter;
// Monitor for the current ambient light mode. Used to trigger / exit low-light mode.
private final AmbientLightModeMonitor mAmbientLightModeMonitor;
// Delayed callback for starting idling.
private final Runnable mEnableIdlingCallback = () -> {
if (DEBUG) {
Log.d(TAG, "time out, should start idling");
}
setState(STATE_SHOULD_START_IDLING, true);
};
private final KeyguardStateController.Callback mKeyguardCallback =
new KeyguardStateController.Callback() {
@Override
@@ -202,29 +147,20 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
@Inject
protected IdleHostViewController(
Context context,
BroadcastDispatcher broadcastDispatcher,
PowerManager powerManager,
IdleHostView view, InputMonitorFactory factory,
@Main DelayableExecutor delayableExecutor,
IdleHostView view,
@Main Resources resources,
@Main Looper looper,
@Named(IDLE_VIEW) Provider<View> idleViewProvider,
Choreographer choreographer,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
DreamHelper dreamHelper,
AmbientLightModeMonitor ambientLightModeMonitor) {
super(view);
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mPowerManager = powerManager;
mIdleViewProvider = idleViewProvider;
mKeyguardStateController = keyguardStateController;
mStatusBarStateController = statusBarStateController;
mLooper = looper;
mChoreographer = choreographer;
mDreamHelper = dreamHelper;
mAmbientLightModeMonitor = ambientLightModeMonitor;
mState = STATE_KEYGUARD_SHOWING;
@@ -236,13 +172,8 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
setState(mState, true);
mIdleTimeout = resources.getInteger(R.integer.config_idleModeTimeout);
mInputMonitorFactory = factory;
mDelayableExecutor = delayableExecutor;
if (DEBUG) {
Log.d(TAG, "initial state:" + mState + " enabled:" + enabled
+ " timeout:" + mIdleTimeout);
Log.d(TAG, "initial state:" + mState + " enabled:" + enabled);
}
}
@@ -255,20 +186,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
}
private void setState(@State int state, boolean active) {
// If waiting for dozing to stop, ignore any state update until dozing is stopped.
if (mDozeToDreamLock) {
if (state == STATE_DOZING && !active) {
if (DEBUG) {
Log.d(TAG, "dozing stopped, now start dreaming");
}
mDozeToDreamLock = false;
enableIdleMode(true);
}
return;
}
final int oldState = mState;
if (active) {
@@ -281,53 +198,20 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
return;
}
// Updates STATE_IDLING.
final boolean isIdling = getState(STATE_DOZING) || getState(STATE_DREAMING);
if (isIdling) {
mState |= STATE_IDLING;
} else {
mState &= ~STATE_IDLING;
}
// Updates STATE_SHOULD_START_IDLING.
final boolean stoppedIdling = stoppedIdling(oldState);
if (stoppedIdling) {
mState &= ~STATE_SHOULD_START_IDLING;
} else if (shouldStartIdling(oldState)) {
mState |= STATE_SHOULD_START_IDLING;
}
if (DEBUG) {
Log.d(TAG, "set " + getStateName(state) + " to " + active);
logCurrentState();
}
final boolean wasLowLight = getState(STATE_LOW_LIGHT, oldState);
final boolean isLowLight = getState(STATE_LOW_LIGHT);
final boolean wasIdling = getState(STATE_IDLING, oldState);
// When the device is idling and no longer in low light, wake up from dozing, wait till
// done, and start dreaming.
if (wasLowLight && !isLowLight && wasIdling && isIdling) {
if (DEBUG) {
Log.d(TAG, "idling and no longer in low light, stop dozing");
}
mDozeToDreamLock = true;
enableLowLightMode(false);
return;
}
final boolean inCommunalMode = getState(STATE_IDLE_MODE_ENABLED)
&& getState(STATE_KEYGUARD_SHOWING);
enableDreamMonitoring(inCommunalMode);
enableLowLightMonitoring(inCommunalMode);
enableIdleMonitoring(inCommunalMode && !getState(STATE_IDLING));
enableIdleMode(inCommunalMode && !getState(STATE_LOW_LIGHT)
&& getState(STATE_SHOULD_START_IDLING));
enableLowLightMode(inCommunalMode && !stoppedIdling && getState(STATE_LOW_LIGHT));
if (state == STATE_LOW_LIGHT) {
enableLowLightMode(inCommunalMode && active);
}
}
private void enableDreamMonitoring(boolean enable) {
@@ -354,64 +238,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
}
}
private void enableIdleMonitoring(boolean enable) {
if (enable && mInputMonitor == null && mInputEventReceiver == null) {
if (DEBUG) {
Log.d(TAG, "enable idle monitoring");
}
// Set initial timeout to idle.
mCancelEnableIdling = mDelayableExecutor.executeDelayed(mEnableIdlingCallback,
mIdleTimeout);
// Monitor - any input should reset timer
mInputMonitor = mInputMonitorFactory.getInputMonitor(INPUT_MONITOR_IDENTIFIER);
mInputEventReceiver = mInputMonitor.getInputReceiver(mLooper, mChoreographer,
v -> {
if (DEBUG) {
Log.d(TAG, "touch detected, resetting timeout");
}
// When input is received, reset timeout.
if (mCancelEnableIdling != null) {
mCancelEnableIdling.run();
mCancelEnableIdling = null;
}
mCancelEnableIdling = mDelayableExecutor.executeDelayed(
mEnableIdlingCallback, mIdleTimeout);
});
} else if (!enable && mInputMonitor != null && mInputEventReceiver != null) {
if (DEBUG) {
Log.d(TAG, "disable idle monitoring");
}
// Clean up idle callback and touch monitoring.
if (mCancelEnableIdling != null) {
mCancelEnableIdling.run();
mCancelEnableIdling = null;
}
mInputEventReceiver.dispose();
mInputMonitor.dispose();
mInputEventReceiver = null;
mInputMonitor = null;
}
}
private void enableIdleMode(boolean enable) {
if (mIdleModeActive == enable) {
return;
}
if (DEBUG) {
Log.d(TAG, (enable ? "enable" : "disable") + " idle mode");
}
mIdleModeActive = enable;
if (mIdleModeActive) {
// Start dream.
mDreamHelper.startDreaming(mContext);
}
}
private void enableLowLightMonitoring(boolean enable) {
if (enable == mIsMonitoringLowLight) {
return;
@@ -429,13 +255,11 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
}
private void enableLowLightMode(boolean enable) {
if (mLowLightModeActive == enable) {
if (enable == getState(STATE_DOZING)) {
return;
}
mLowLightModeActive = enable;
if (mLowLightModeActive) {
if (enable) {
if (DEBUG) Log.d(TAG, "enter low light, start dozing");
mPowerManager.goToSleep(
@@ -464,19 +288,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
mStatusBarStateController.removeCallback(mStatusBarCallback);
}
// Returns whether the device just stopped idling by comparing the previous state with the
// current one.
private boolean stoppedIdling(int oldState) {
// The device stopped idling if it's no longer dreaming or dozing.
return !getState(STATE_DOZING) && !getState(STATE_DREAMING)
&& (getState(STATE_DOZING, oldState) || getState(STATE_DREAMING, oldState));
}
private boolean shouldStartIdling(int oldState) {
// Should start idling immediately if the device went in low light environment.
return !getState(STATE_LOW_LIGHT, oldState) && getState(STATE_LOW_LIGHT);
}
private String getStateName(@State int state) {
switch (state) {
case STATE_IDLE_MODE_ENABLED:
@@ -489,10 +300,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
return "STATE_DREAMING";
case STATE_LOW_LIGHT:
return "STATE_LOW_LIGHT";
case STATE_IDLING:
return "STATE_IDLING";
case STATE_SHOULD_START_IDLING:
return "STATE_SHOULD_START_IDLING";
default:
return "STATE_UNKNOWN";
}
@@ -517,8 +324,6 @@ public class IdleHostViewController extends ViewController<IdleHostView> {
+ "\t" + getStateLog(STATE_DOZING) + "\n"
+ "\t" + getStateLog(STATE_DREAMING) + "\n"
+ "\t" + getStateLog(STATE_LOW_LIGHT) + "\n"
+ "\t" + getStateLog(STATE_IDLING) + "\n"
+ "\t" + getStateLog(STATE_SHOULD_START_IDLING) + "\n"
+ "}");
}
}

View File

@@ -16,22 +16,16 @@
package com.android.systemui.idle;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Looper;
import android.os.PowerManager;
import android.testing.AndroidTestingRunner;
import android.view.Choreographer;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -40,10 +34,7 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import org.junit.Before;
import org.junit.Test;
@@ -60,22 +51,14 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private PowerManager mPowerManager;
@Mock private IdleHostView mIdleHostView;
@Mock private InputMonitorFactory mInputMonitorFactory;
@Mock private DelayableExecutor mDelayableExecutor;
@Mock private Resources mResources;
@Mock private Looper mLooper;
@Mock private Provider<View> mViewProvider;
@Mock private Choreographer mChoreographer;
@Mock private KeyguardStateController mKeyguardStateController;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private DreamHelper mDreamHelper;
@Mock private InputMonitorCompat mInputMonitor;
@Mock private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@Mock private AmbientLightModeMonitor mAmbientLightModeMonitor;
private KeyguardStateController.Callback mKeyguardStateCallback;
private StatusBarStateController.StateListener mStatusBarStateListener;
private IdleHostViewController mController;
@Before
public void setup() {
@@ -83,17 +66,12 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
when(mResources.getBoolean(R.bool.config_enableIdleMode)).thenReturn(true);
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mInputMonitorFactory.getInputMonitor("IdleHostViewController"))
.thenReturn(mInputMonitor);
when(mInputMonitor.getInputReceiver(any(), any(), any())).thenReturn(mInputEventReceiver);
mController = new IdleHostViewController(mContext,
mBroadcastDispatcher, mPowerManager, mIdleHostView,
mInputMonitorFactory, mDelayableExecutor, mResources, mLooper, mViewProvider,
mChoreographer, mKeyguardStateController, mStatusBarStateController, mDreamHelper,
mAmbientLightModeMonitor);
mController.init();
mController.onViewAttached();
final IdleHostViewController controller = new IdleHostViewController(mBroadcastDispatcher,
mPowerManager, mIdleHostView, mResources, mViewProvider, mKeyguardStateController,
mStatusBarStateController, mAmbientLightModeMonitor);
controller.init();
controller.onViewAttached();
// Captures keyguard state controller callback.
ArgumentCaptor<KeyguardStateController.Callback> keyguardStateCallbackCaptor =
@@ -108,101 +86,6 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
mStatusBarStateListener = statusBarStateListenerCaptor.getValue();
}
@Test
public void testTimeoutToIdleMode() {
// Keyguard showing.
when(mKeyguardStateController.isShowing()).thenReturn(true);
mKeyguardStateCallback.onKeyguardShowingChanged();
// Regular ambient lighting.
final AmbientLightModeMonitor.Callback lightMonitorCallback =
captureAmbientLightModeMonitorCallback();
lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
// Times out.
ArgumentCaptor<Runnable> callbackCapture = ArgumentCaptor.forClass(Runnable.class);
verify(mDelayableExecutor).executeDelayed(callbackCapture.capture(), anyLong());
callbackCapture.getValue().run();
// Verifies start dreaming (idle mode).
verify(mDreamHelper).startDreaming(any());
}
@Test
public void testTimeoutToLowLightMode() {
// Keyguard showing.
when(mKeyguardStateController.isShowing()).thenReturn(true);
mKeyguardStateCallback.onKeyguardShowingChanged();
// Captures dream broadcast receiver;
ArgumentCaptor<BroadcastReceiver> dreamBroadcastReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mBroadcastDispatcher)
.registerReceiver(dreamBroadcastReceiverCaptor.capture(), any());
final BroadcastReceiver dreamBroadcastReceiver = dreamBroadcastReceiverCaptor.getValue();
// Low ambient lighting.
final AmbientLightModeMonitor.Callback lightMonitorCallback =
captureAmbientLightModeMonitorCallback();
lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
// Verifies it goes to sleep because of low light.
verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
mStatusBarStateListener.onDozingChanged(true /*isDozing*/);
dreamBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STARTED));
// User wakes up the device.
mStatusBarStateListener.onDozingChanged(false /*isDozing*/);
dreamBroadcastReceiver.onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED));
// Clears power manager invocations to make sure the below dozing was triggered by the
// timeout.
clearInvocations(mPowerManager);
// Times out.
ArgumentCaptor<Runnable> callbackCapture = ArgumentCaptor.forClass(Runnable.class);
verify(mDelayableExecutor, atLeastOnce()).executeDelayed(callbackCapture.capture(),
anyLong());
callbackCapture.getValue().run();
// Verifies go to sleep (low light mode).
verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
}
@Test
public void testTransitionBetweenIdleAndLowLightMode() {
// Keyguard showing.
when(mKeyguardStateController.isShowing()).thenReturn(true);
mKeyguardStateCallback.onKeyguardShowingChanged();
// Regular ambient lighting.
final AmbientLightModeMonitor.Callback lightMonitorCallback =
captureAmbientLightModeMonitorCallback();
lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
// Times out.
ArgumentCaptor<Runnable> callbackCapture = ArgumentCaptor.forClass(Runnable.class);
verify(mDelayableExecutor).executeDelayed(callbackCapture.capture(), anyLong());
callbackCapture.getValue().run();
// Verifies in idle mode (dreaming).
verify(mDreamHelper).startDreaming(any());
clearInvocations(mDreamHelper);
// Ambient lighting becomes dim.
lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
// Verifies in low light mode (dozing).
verify(mPowerManager).goToSleep(anyLong(), anyInt(), anyInt());
// Ambient lighting becomes bright again.
lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
// Verifies in idle mode (dreaming).
verify(mDreamHelper).startDreaming(any());
}
@Test
public void testStartDozingWhenLowLight() {
// Keyguard showing.
@@ -225,20 +108,22 @@ public class IdleHostViewControllerTest extends SysuiTestCase {
}
@Test
public void testInputEventReceiverLifecycle() {
public void testWakeUpWhenRegularLight() {
// Keyguard showing.
when(mKeyguardStateController.isShowing()).thenReturn(true);
mKeyguardStateCallback.onKeyguardShowingChanged();
// Should register input event receiver.
verify(mInputMonitor).getInputReceiver(any(), any(), any());
// In low light / dozing.
final AmbientLightModeMonitor.Callback lightMonitorCallback =
captureAmbientLightModeMonitorCallback();
lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_DARK);
mStatusBarStateListener.onDozingChanged(true /*isDozing*/);
// Keyguard dismissed.
when(mKeyguardStateController.isShowing()).thenReturn(false);
mKeyguardStateCallback.onKeyguardShowingChanged();
// Regular ambient lighting.
lightMonitorCallback.onChange(AmbientLightModeMonitor.AMBIENT_LIGHT_MODE_LIGHT);
// Should dispose input event receiver.
verify(mInputEventReceiver).dispose();
// Verifies it wakes up from sleep.
verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
}
// Captures [AmbientLightModeMonitor.Callback] assuming that the ambient light mode monitor