• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 android.app.AlarmManager;
20 import android.app.UiModeManager;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.res.Configuration;
26 import android.hardware.Sensor;
27 import android.hardware.SensorEvent;
28 import android.hardware.SensorEventListener;
29 import android.hardware.SensorManager;
30 import android.os.Handler;
31 import android.os.SystemClock;
32 import android.os.UserHandle;
33 import android.text.format.Formatter;
34 import android.util.Log;
35 
36 import com.android.internal.hardware.AmbientDisplayConfiguration;
37 import com.android.internal.util.Preconditions;
38 import com.android.systemui.statusbar.phone.DozeParameters;
39 import com.android.systemui.util.Assert;
40 import com.android.systemui.util.wakelock.WakeLock;
41 
42 import java.io.PrintWriter;
43 import java.util.function.IntConsumer;
44 
45 /**
46  * Handles triggers for ambient state changes.
47  */
48 public class DozeTriggers implements DozeMachine.Part {
49 
50     private static final String TAG = "DozeTriggers";
51     private static final boolean DEBUG = DozeService.DEBUG;
52 
53     /** adb shell am broadcast -a com.android.systemui.doze.pulse com.android.systemui */
54     private static final String PULSE_ACTION = "com.android.systemui.doze.pulse";
55 
56     private final Context mContext;
57     private final DozeMachine mMachine;
58     private final DozeSensors mDozeSensors;
59     private final DozeHost mDozeHost;
60     private final AmbientDisplayConfiguration mConfig;
61     private final DozeParameters mDozeParameters;
62     private final SensorManager mSensorManager;
63     private final Handler mHandler;
64     private final WakeLock mWakeLock;
65     private final boolean mAllowPulseTriggers;
66     private final UiModeManager mUiModeManager;
67     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
68 
69     private long mNotificationPulseTime;
70     private boolean mPulsePending;
71 
72 
DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, AlarmManager alarmManager, AmbientDisplayConfiguration config, DozeParameters dozeParameters, SensorManager sensorManager, Handler handler, WakeLock wakeLock, boolean allowPulseTriggers)73     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
74             AlarmManager alarmManager, AmbientDisplayConfiguration config,
75             DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
76             WakeLock wakeLock, boolean allowPulseTriggers) {
77         mContext = context;
78         mMachine = machine;
79         mDozeHost = dozeHost;
80         mConfig = config;
81         mDozeParameters = dozeParameters;
82         mSensorManager = sensorManager;
83         mHandler = handler;
84         mWakeLock = wakeLock;
85         mAllowPulseTriggers = allowPulseTriggers;
86         mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
87                 config, wakeLock, this::onSensor, this::onProximityFar,
88                 dozeParameters.getPolicy());
89         mUiModeManager = mContext.getSystemService(UiModeManager.class);
90     }
91 
onNotification()92     private void onNotification() {
93         if (DozeMachine.DEBUG) Log.d(TAG, "requestNotificationPulse");
94         mNotificationPulseTime = SystemClock.elapsedRealtime();
95         if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) return;
96         requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */);
97         DozeLog.traceNotificationPulse(mContext);
98     }
99 
onWhisper()100     private void onWhisper() {
101         requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */);
102     }
103 
proximityCheckThenCall(IntConsumer callback, boolean alreadyPerformedProxCheck, int pulseReason)104     private void proximityCheckThenCall(IntConsumer callback,
105             boolean alreadyPerformedProxCheck,
106             int pulseReason) {
107         Boolean cachedProxFar = mDozeSensors.isProximityCurrentlyFar();
108         if (alreadyPerformedProxCheck) {
109             callback.accept(ProximityCheck.RESULT_NOT_CHECKED);
110         } else if (cachedProxFar != null) {
111             callback.accept(cachedProxFar ? ProximityCheck.RESULT_FAR : ProximityCheck.RESULT_NEAR);
112         } else {
113             final long start = SystemClock.uptimeMillis();
114             new ProximityCheck() {
115                 @Override
116                 public void onProximityResult(int result) {
117                     final long end = SystemClock.uptimeMillis();
118                     DozeLog.traceProximityResult(mContext, result == RESULT_NEAR,
119                             end - start, pulseReason);
120                     callback.accept(result);
121                 }
122             }.check();
123         }
124     }
125 
onSensor(int pulseReason, boolean sensorPerformedProxCheck, float screenX, float screenY)126     private void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
127             float screenX, float screenY) {
128         boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
129         boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
130         boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS;
131 
132         if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !isLongPress) {
133             proximityCheckThenCall((result) -> {
134                 if (result == ProximityCheck.RESULT_NEAR) {
135                     // In pocket, drop event.
136                     return;
137                 }
138                 if (isDoubleTap) {
139                     mDozeHost.onDoubleTap(screenX, screenY);
140                     mMachine.wakeUp();
141                 } else {
142                     mDozeHost.extendPulse();
143                 }
144             }, sensorPerformedProxCheck, pulseReason);
145             return;
146         } else {
147             requestPulse(pulseReason, sensorPerformedProxCheck);
148         }
149 
150         if (isPickup) {
151             final long timeSinceNotification =
152                     SystemClock.elapsedRealtime() - mNotificationPulseTime;
153             final boolean withinVibrationThreshold =
154                     timeSinceNotification < mDozeParameters.getPickupVibrationThreshold();
155             DozeLog.tracePickupPulse(mContext, withinVibrationThreshold);
156         }
157     }
158 
159     private void onProximityFar(boolean far) {
160         final boolean near = !far;
161         final DozeMachine.State state = mMachine.getState();
162         final boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED);
163         final boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING);
164         final boolean aod = (state == DozeMachine.State.DOZE_AOD);
165 
166         if (state == DozeMachine.State.DOZE_PULSING) {
167             boolean ignoreTouch = near;
168             if (DEBUG) Log.i(TAG, "Prox changed, ignore touch = " + ignoreTouch);
169             mDozeHost.onIgnoreTouchWhilePulsing(ignoreTouch);
170         }
171         if (far && (paused || pausing)) {
172             if (DEBUG) Log.i(TAG, "Prox FAR, unpausing AOD");
173             mMachine.requestState(DozeMachine.State.DOZE_AOD);
174         } else if (near && aod) {
175             if (DEBUG) Log.i(TAG, "Prox NEAR, pausing AOD");
176             mMachine.requestState(DozeMachine.State.DOZE_AOD_PAUSING);
177         }
178     }
179 
180     @Override
181     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
182         switch (newState) {
183             case INITIALIZED:
184                 mBroadcastReceiver.register(mContext);
185                 mDozeHost.addCallback(mHostCallback);
186                 checkTriggersAtInit();
187                 break;
188             case DOZE:
189             case DOZE_AOD:
190                 mDozeSensors.setProxListening(newState != DozeMachine.State.DOZE);
191                 if (oldState != DozeMachine.State.INITIALIZED) {
192                     mDozeSensors.reregisterAllSensors();
193                 }
194                 mDozeSensors.setListening(true);
195                 break;
196             case DOZE_AOD_PAUSED:
197             case DOZE_AOD_PAUSING:
198                 mDozeSensors.setProxListening(true);
199                 mDozeSensors.setListening(false);
200                 break;
201             case DOZE_PULSING:
202                 mDozeSensors.setTouchscreenSensorsListening(false);
203                 mDozeSensors.setProxListening(true);
204                 break;
205             case FINISH:
206                 mBroadcastReceiver.unregister(mContext);
207                 mDozeHost.removeCallback(mHostCallback);
208                 mDozeSensors.setListening(false);
209                 mDozeSensors.setProxListening(false);
210                 break;
211             default:
212         }
213     }
214 
215     private void checkTriggersAtInit() {
216         if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
217                 || mDozeHost.isPowerSaveActive()
218                 || mDozeHost.isBlockingDoze()
219                 || !mDozeHost.isProvisioned()) {
220             mMachine.requestState(DozeMachine.State.FINISH);
221         }
222     }
223 
224     private void requestPulse(final int reason, boolean performedProxCheck) {
225         Assert.isMainThread();
226         mDozeHost.extendPulse();
227         if (mPulsePending || !mAllowPulseTriggers || !canPulse()) {
228             if (mAllowPulseTriggers) {
229                 DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(),
230                         mDozeHost.isPulsingBlocked());
231             }
232             return;
233         }
234 
235         mPulsePending = true;
236         proximityCheckThenCall((result) -> {
237             if (result == ProximityCheck.RESULT_NEAR) {
238                 // in pocket, abort pulse
239                 mPulsePending = false;
240             } else {
241                 // not in pocket, continue pulsing
242                 continuePulseRequest(reason);
243             }
244         }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
245     }
246 
canPulse()247     private boolean canPulse() {
248         return mMachine.getState() == DozeMachine.State.DOZE
249                 || mMachine.getState() == DozeMachine.State.DOZE_AOD;
250     }
251 
continuePulseRequest(int reason)252     private void continuePulseRequest(int reason) {
253         mPulsePending = false;
254         if (mDozeHost.isPulsingBlocked() || !canPulse()) {
255             DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(),
256                     mDozeHost.isPulsingBlocked());
257             return;
258         }
259         mMachine.requestPulse(reason);
260     }
261 
262     @Override
dump(PrintWriter pw)263     public void dump(PrintWriter pw) {
264         pw.print(" notificationPulseTime=");
265         pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
266 
267         pw.print(" pulsePending="); pw.println(mPulsePending);
268         pw.println("DozeSensors:");
269         mDozeSensors.dump(pw);
270     }
271 
272     private abstract class ProximityCheck implements SensorEventListener, Runnable {
273         private static final int TIMEOUT_DELAY_MS = 500;
274 
275         protected static final int RESULT_UNKNOWN = 0;
276         protected static final int RESULT_NEAR = 1;
277         protected static final int RESULT_FAR = 2;
278         protected static final int RESULT_NOT_CHECKED = 3;
279 
280         private boolean mRegistered;
281         private boolean mFinished;
282         private float mMaxRange;
283 
284         protected abstract void onProximityResult(int result);
285 
check()286         public void check() {
287             Preconditions.checkState(!mFinished && !mRegistered);
288             final Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
289             if (sensor == null) {
290                 if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found");
291                 finishWithResult(RESULT_UNKNOWN);
292                 return;
293             }
294             mDozeSensors.setDisableSensorsInterferingWithProximity(true);
295 
296             mMaxRange = sensor.getMaximumRange();
297             mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
298                     mHandler);
299             mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
300             mWakeLock.acquire();
301             mRegistered = true;
302         }
303 
304         @Override
onSensorChanged(SensorEvent event)305         public void onSensorChanged(SensorEvent event) {
306             if (event.values.length == 0) {
307                 if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!");
308                 finishWithResult(RESULT_UNKNOWN);
309             } else {
310                 if (DozeMachine.DEBUG) {
311                     Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
312                 }
313                 final boolean isNear = event.values[0] < mMaxRange;
314                 finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
315             }
316         }
317 
318         @Override
319         public void run() {
320             if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout");
321             finishWithResult(RESULT_UNKNOWN);
322         }
323 
324         private void finishWithResult(int result) {
325             if (mFinished) return;
326             boolean wasRegistered = mRegistered;
327             if (mRegistered) {
328                 mHandler.removeCallbacks(this);
329                 mSensorManager.unregisterListener(this);
330                 mDozeSensors.setDisableSensorsInterferingWithProximity(false);
331                 mRegistered = false;
332             }
333             onProximityResult(result);
334             if (wasRegistered) {
335                 mWakeLock.release();
336             }
337             mFinished = true;
338         }
339 
340         @Override
341         public void onAccuracyChanged(Sensor sensor, int accuracy) {
342             // noop
343         }
344     }
345 
346     private class TriggerReceiver extends BroadcastReceiver {
347         private boolean mRegistered;
348 
349         @Override
350         public void onReceive(Context context, Intent intent) {
351             if (PULSE_ACTION.equals(intent.getAction())) {
352                 if (DozeMachine.DEBUG) Log.d(TAG, "Received pulse intent");
353                 requestPulse(DozeLog.PULSE_REASON_INTENT, false /* performedProxCheck */);
354             }
355             if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
356                 mMachine.requestState(DozeMachine.State.FINISH);
357             }
358             if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
359                 mDozeSensors.onUserSwitched();
360             }
361         }
362 
363         public void register(Context context) {
364             if (mRegistered) {
365                 return;
366             }
367             IntentFilter filter = new IntentFilter(PULSE_ACTION);
368             filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE);
369             filter.addAction(Intent.ACTION_USER_SWITCHED);
370             context.registerReceiver(this, filter);
371             mRegistered = true;
372         }
373 
374         public void unregister(Context context) {
375             if (!mRegistered) {
376                 return;
377             }
378             context.unregisterReceiver(this);
379             mRegistered = false;
380         }
381     }
382 
383     private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
384         @Override
385         public void onNotificationHeadsUp() {
386             onNotification();
387         }
388 
389         @Override
390         public void onPowerSaveChanged(boolean active) {
391             if (active) {
392                 mMachine.requestState(DozeMachine.State.FINISH);
393             }
394         }
395     };
396 }
397