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