1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.doze;
18 
19 import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
20 
21 import android.hardware.display.AmbientDisplayConfiguration;
22 import android.os.PowerManager;
23 import android.text.TextUtils;
24 
25 import com.android.systemui.doze.dagger.DozeScope;
26 import com.android.systemui.settings.UserTracker;
27 import com.android.systemui.statusbar.phone.BiometricUnlockController;
28 
29 import java.io.PrintWriter;
30 
31 import javax.inject.Inject;
32 
33 import dagger.Lazy;
34 
35 /**
36  * Handles suppressing doze on:
37  * 1. INITIALIZED, don't allow dozing at all when:
38  *      - in CAR_MODE, in this scenario the device is asleep and won't listen for any triggers
39  *      to wake up. In this state, no UI shows. Unlike other conditions, this suppression is only
40  *      temporary and stops when the device exits CAR_MODE
41  *      - device is NOT provisioned
42  *      - there's a pending authentication
43  * 2. PowerSaveMode active
44  *      - no always-on-display (DOZE_AOD)
45  *      - continues to allow doze triggers (DOZE, DOZE_REQUEST_PULSE)
46  * 3. Suppression changes from the PowerManager API. See {@link PowerManager#suppressAmbientDisplay}
47  *      and {@link DozeHost#isAlwaysOnSuppressed()}.
48  *      - no always-on-display (DOZE_AOD)
49  *      - allow doze triggers (DOZE), but disallow notifications (handled by {@link DozeTriggers})
50  *      - See extra check in {@link DozeMachine} to guarantee device never enters always-on states
51  */
52 @DozeScope
53 public class DozeSuppressor implements DozeMachine.Part {
54 
55     private DozeMachine mMachine;
56     private final DozeHost mDozeHost;
57     private final AmbientDisplayConfiguration mConfig;
58     private final DozeLog mDozeLog;
59     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
60     private final UserTracker mUserTracker;
61 
62     private boolean mIsCarModeEnabled = false;
63 
64     @Inject
DozeSuppressor( DozeHost dozeHost, AmbientDisplayConfiguration config, DozeLog dozeLog, Lazy<BiometricUnlockController> biometricUnlockControllerLazy, UserTracker userTracker)65     public DozeSuppressor(
66             DozeHost dozeHost,
67             AmbientDisplayConfiguration config,
68             DozeLog dozeLog,
69             Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
70             UserTracker userTracker) {
71         mDozeHost = dozeHost;
72         mConfig = config;
73         mDozeLog = dozeLog;
74         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
75         mUserTracker = userTracker;
76     }
77 
78     @Override
onUiModeTypeChanged(int newUiModeType)79     public void onUiModeTypeChanged(int newUiModeType) {
80         boolean isCarModeEnabled = newUiModeType == UI_MODE_TYPE_CAR;
81         if (mIsCarModeEnabled == isCarModeEnabled) {
82             return;
83         }
84         mIsCarModeEnabled = isCarModeEnabled;
85         // Do not handle the event if doze machine is not initialized yet.
86         // It will be handled upon initialization.
87         if (mMachine.isUninitializedOrFinished()) {
88             return;
89         }
90         if (mIsCarModeEnabled) {
91             handleCarModeStarted();
92         } else {
93             handleCarModeExited();
94         }
95     }
96 
97     @Override
setDozeMachine(DozeMachine dozeMachine)98     public void setDozeMachine(DozeMachine dozeMachine) {
99         mMachine = dozeMachine;
100     }
101 
102     @Override
transitionTo(DozeMachine.State oldState, DozeMachine.State newState)103     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
104         switch (newState) {
105             case INITIALIZED:
106                 mDozeHost.addCallback(mHostCallback);
107                 checkShouldImmediatelyEndDoze();
108                 checkShouldImmediatelySuspendDoze();
109                 break;
110             case FINISH:
111                 destroy();
112                 break;
113             default:
114         }
115     }
116 
117     @Override
destroy()118     public void destroy() {
119         mDozeHost.removeCallback(mHostCallback);
120     }
121 
checkShouldImmediatelySuspendDoze()122     private void checkShouldImmediatelySuspendDoze() {
123         if (mIsCarModeEnabled) {
124             handleCarModeStarted();
125         }
126     }
127 
checkShouldImmediatelyEndDoze()128     private void checkShouldImmediatelyEndDoze() {
129         String reason = null;
130         if (!mDozeHost.isProvisioned()) {
131             reason = "device_unprovisioned";
132         } else if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) {
133             reason = "has_pending_auth";
134         }
135 
136         if (!TextUtils.isEmpty(reason)) {
137             mDozeLog.traceImmediatelyEndDoze(reason);
138             mMachine.requestState(DozeMachine.State.FINISH);
139         }
140     }
141 
142     @Override
dump(PrintWriter pw)143     public void dump(PrintWriter pw) {
144         pw.println(" isCarModeEnabled=" + mIsCarModeEnabled);
145         pw.println(" hasPendingAuth="
146                 + mBiometricUnlockControllerLazy.get().hasPendingAuthentication());
147         pw.println(" isProvisioned=" + mDozeHost.isProvisioned());
148         pw.println(" isAlwaysOnSuppressed=" + mDozeHost.isAlwaysOnSuppressed());
149         pw.println(" aodPowerSaveActive=" + mDozeHost.isPowerSaveActive());
150     }
151 
handleCarModeExited()152     private void handleCarModeExited() {
153         mDozeLog.traceCarModeEnded();
154         mMachine.requestState(mConfig.alwaysOnEnabled(mUserTracker.getUserId())
155                 ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
156     }
157 
handleCarModeStarted()158     private void handleCarModeStarted() {
159         mDozeLog.traceCarModeStarted();
160         mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
161     }
162 
163     private final DozeHost.Callback mHostCallback = new DozeHost.Callback() {
164         @Override
165         public void onPowerSaveChanged(boolean active) {
166             // handles suppression changes, while DozeMachine#transitionPolicy handles gating
167             // transitions to DOZE_AOD
168             DozeMachine.State nextState = null;
169             if (mDozeHost.isPowerSaveActive()) {
170                 nextState = DozeMachine.State.DOZE;
171             } else if (mMachine.getState() == DozeMachine.State.DOZE
172                     && mConfig.alwaysOnEnabled(mUserTracker.getUserId())) {
173                 nextState = DozeMachine.State.DOZE_AOD;
174             }
175 
176             if (nextState != null) {
177                 mDozeLog.tracePowerSaveChanged(mDozeHost.isPowerSaveActive(), nextState);
178                 mMachine.requestState(nextState);
179             }
180         }
181 
182         @Override
183         public void onAlwaysOnSuppressedChanged(boolean suppressed) {
184             // handles suppression changes, while DozeMachine#transitionPolicy handles gating
185             // transitions to DOZE_AOD
186             final DozeMachine.State nextState;
187             if (mConfig.alwaysOnEnabled(mUserTracker.getUserId()) && !suppressed) {
188                 nextState = DozeMachine.State.DOZE_AOD;
189             } else {
190                 nextState = DozeMachine.State.DOZE;
191             }
192             mDozeLog.traceAlwaysOnSuppressedChange(suppressed, nextState);
193             mMachine.requestState(nextState);
194         }
195     };
196 }
197