/* * Copyright (C) 2022 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 static android.content.res.Configuration.UI_MODE_TYPE_CAR; import android.hardware.display.AmbientDisplayConfiguration; import android.os.PowerManager; import android.text.TextUtils; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.phone.BiometricUnlockController; import java.io.PrintWriter; import javax.inject.Inject; import dagger.Lazy; /** * Handles suppressing doze on: * 1. INITIALIZED, don't allow dozing at all when: * - in CAR_MODE, in this scenario the device is asleep and won't listen for any triggers * to wake up. In this state, no UI shows. Unlike other conditions, this suppression is only * temporary and stops when the device exits CAR_MODE * - device is NOT provisioned * - there's a pending authentication * 2. PowerSaveMode active * - no always-on-display (DOZE_AOD) * - continues to allow doze triggers (DOZE, DOZE_REQUEST_PULSE) * 3. Suppression changes from the PowerManager API. See {@link PowerManager#suppressAmbientDisplay} * and {@link DozeHost#isAlwaysOnSuppressed()}. * - no always-on-display (DOZE_AOD) * - allow doze triggers (DOZE), but disallow notifications (handled by {@link DozeTriggers}) * - See extra check in {@link DozeMachine} to guarantee device never enters always-on states */ @DozeScope public class DozeSuppressor implements DozeMachine.Part { private DozeMachine mMachine; private final DozeHost mDozeHost; private final AmbientDisplayConfiguration mConfig; private final DozeLog mDozeLog; private final Lazy mBiometricUnlockControllerLazy; private final UserTracker mUserTracker; private boolean mIsCarModeEnabled = false; @Inject public DozeSuppressor( DozeHost dozeHost, AmbientDisplayConfiguration config, DozeLog dozeLog, Lazy biometricUnlockControllerLazy, UserTracker userTracker) { mDozeHost = dozeHost; mConfig = config; mDozeLog = dozeLog; mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; mUserTracker = userTracker; } @Override public void onUiModeTypeChanged(int newUiModeType) { boolean isCarModeEnabled = newUiModeType == UI_MODE_TYPE_CAR; if (mIsCarModeEnabled == isCarModeEnabled) { return; } mIsCarModeEnabled = isCarModeEnabled; // Do not handle the event if doze machine is not initialized yet. // It will be handled upon initialization. if (mMachine.isUninitializedOrFinished()) { return; } if (mIsCarModeEnabled) { handleCarModeStarted(); } else { handleCarModeExited(); } } @Override public void setDozeMachine(DozeMachine dozeMachine) { mMachine = dozeMachine; } @Override public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { switch (newState) { case INITIALIZED: mDozeHost.addCallback(mHostCallback); checkShouldImmediatelyEndDoze(); checkShouldImmediatelySuspendDoze(); break; case FINISH: destroy(); break; default: } } @Override public void destroy() { mDozeHost.removeCallback(mHostCallback); } private void checkShouldImmediatelySuspendDoze() { if (mIsCarModeEnabled) { handleCarModeStarted(); } } private void checkShouldImmediatelyEndDoze() { String reason = null; if (!mDozeHost.isProvisioned()) { reason = "device_unprovisioned"; } else if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) { reason = "has_pending_auth"; } if (!TextUtils.isEmpty(reason)) { mDozeLog.traceImmediatelyEndDoze(reason); mMachine.requestState(DozeMachine.State.FINISH); } } @Override public void dump(PrintWriter pw) { pw.println(" isCarModeEnabled=" + mIsCarModeEnabled); pw.println(" hasPendingAuth=" + mBiometricUnlockControllerLazy.get().hasPendingAuthentication()); pw.println(" isProvisioned=" + mDozeHost.isProvisioned()); pw.println(" isAlwaysOnSuppressed=" + mDozeHost.isAlwaysOnSuppressed()); pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive()); } private void handleCarModeExited() { mDozeLog.traceCarModeEnded(); mMachine.requestState(mConfig.alwaysOnEnabled(mUserTracker.getUserId()) ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE); } private void handleCarModeStarted() { mDozeLog.traceCarModeStarted(); mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS); } private final DozeHost.Callback mHostCallback = new DozeHost.Callback() { @Override public void onPowerSaveChanged(boolean active) { // handles suppression changes, while DozeMachine#transitionPolicy handles gating // transitions to DOZE_AOD DozeMachine.State nextState = null; if (mDozeHost.isPowerSaveActive()) { nextState = DozeMachine.State.DOZE; } else if (mMachine.getState() == DozeMachine.State.DOZE && mConfig.alwaysOnEnabled(mUserTracker.getUserId())) { nextState = DozeMachine.State.DOZE_AOD; } if (nextState != null) { mDozeLog.tracePowerSaveChanged(mDozeHost.isPowerSaveActive(), nextState); mMachine.requestState(nextState); } } @Override public void onAlwaysOnSuppressedChanged(boolean suppressed) { // handles suppression changes, while DozeMachine#transitionPolicy handles gating // transitions to DOZE_AOD final DozeMachine.State nextState; if (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) && !suppressed) { nextState = DozeMachine.State.DOZE_AOD; } else { nextState = DozeMachine.State.DOZE; } mDozeLog.traceAlwaysOnSuppressedChange(suppressed, nextState); mMachine.requestState(nextState); } }; }