2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.doze;
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;
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;
42 import java.io.PrintWriter;
43 import java.util.function.IntConsumer;
46 * Handles triggers for ambient state changes.
48 public class DozeTriggers implements DozeMachine.Part {
50 private static final String TAG = "DozeTriggers";
51 private static final boolean DEBUG = DozeService.DEBUG;
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";
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();
69 private long mNotificationPulseTime;
70 private boolean mPulsePending;
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) {
81 mDozeParameters = dozeParameters;
82 mSensorManager = sensorManager;
85 mAllowPulseTriggers = allowPulseTriggers;
86 mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters,
87 config, wakeLock, this::onSensor, this::onProximityFar,
88 new AlwaysOnDisplayPolicy(context));
89 mUiModeManager = mContext.getSystemService(UiModeManager.class);
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);
100 private void onWhisper() {
101 requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */);
104 private void proximityCheckThenCall(IntConsumer callback,
105 boolean alreadyPerformedProxCheck,
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);
113 final long start = SystemClock.uptimeMillis();
114 new ProximityCheck() {
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);
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;
132 if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !isLongPress) {
133 proximityCheckThenCall((result) -> {
134 if (result == ProximityCheck.RESULT_NEAR) {
135 // In pocket, drop event.
139 mDozeHost.onDoubleTap(screenX, screenY);
142 mDozeHost.extendPulse();
144 }, sensorPerformedProxCheck, pulseReason);
147 requestPulse(pulseReason, sensorPerformedProxCheck);
151 final long timeSinceNotification =
152 SystemClock.elapsedRealtime() - mNotificationPulseTime;
153 final boolean withinVibrationThreshold =
154 timeSinceNotification < mDozeParameters.getPickupVibrationThreshold();
155 DozeLog.tracePickupPulse(mContext, withinVibrationThreshold);
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);
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);
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);
181 public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
184 mBroadcastReceiver.register(mContext);
185 mDozeHost.addCallback(mHostCallback);
186 checkTriggersAtInit();
190 mDozeSensors.setProxListening(newState != DozeMachine.State.DOZE);
191 if (oldState != DozeMachine.State.INITIALIZED) {
192 mDozeSensors.reregisterAllSensors();
194 mDozeSensors.setListening(true);
196 case DOZE_AOD_PAUSED:
197 case DOZE_AOD_PAUSING:
198 mDozeSensors.setProxListening(true);
199 mDozeSensors.setListening(false);
202 mDozeSensors.setTouchscreenSensorsListening(false);
203 mDozeSensors.setProxListening(true);
206 mBroadcastReceiver.unregister(mContext);
207 mDozeHost.removeCallback(mHostCallback);
208 mDozeSensors.setListening(false);
209 mDozeSensors.setProxListening(false);
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);
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());
235 mPulsePending = true;
236 proximityCheckThenCall((result) -> {
237 if (result == ProximityCheck.RESULT_NEAR) {
238 // in pocket, abort pulse
239 mPulsePending = false;
241 // not in pocket, continue pulsing
242 continuePulseRequest(reason);
244 }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason);
247 private boolean canPulse() {
248 return mMachine.getState() == DozeMachine.State.DOZE
249 || mMachine.getState() == DozeMachine.State.DOZE_AOD;
252 private void continuePulseRequest(int reason) {
253 mPulsePending = false;
254 if (mDozeHost.isPulsingBlocked() || !canPulse()) {
255 DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(),
256 mDozeHost.isPulsingBlocked());
259 mMachine.requestPulse(reason);
263 public void dump(PrintWriter pw) {
264 pw.print(" notificationPulseTime=");
265 pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime));
267 pw.print(" pulsePending="); pw.println(mPulsePending);
268 pw.println("DozeSensors:");
269 mDozeSensors.dump(pw);
272 private abstract class ProximityCheck implements SensorEventListener, Runnable {
273 private static final int TIMEOUT_DELAY_MS = 500;
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;
280 private boolean mRegistered;
281 private boolean mFinished;
282 private float mMaxRange;
284 protected abstract void onProximityResult(int result);
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);
294 mDozeSensors.setDisableSensorsInterferingWithProximity(true);
296 mMaxRange = sensor.getMaximumRange();
297 mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0,
299 mHandler.postDelayed(this, TIMEOUT_DELAY_MS);
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);
310 if (DozeMachine.DEBUG) {
311 Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
313 final boolean isNear = event.values[0] < mMaxRange;
314 finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
320 if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout");
321 finishWithResult(RESULT_UNKNOWN);
324 private void finishWithResult(int result) {
325 if (mFinished) return;
326 boolean wasRegistered = mRegistered;
328 mHandler.removeCallbacks(this);
329 mSensorManager.unregisterListener(this);
330 mDozeSensors.setDisableSensorsInterferingWithProximity(false);
333 onProximityResult(result);
341 public void onAccuracyChanged(Sensor sensor, int accuracy) {
346 private class TriggerReceiver extends BroadcastReceiver {
347 private boolean mRegistered;
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 */);
355 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
356 mMachine.requestState(DozeMachine.State.FINISH);
358 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
359 mDozeSensors.onUserSwitched();
363 public void register(Context context) {
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);
374 public void unregister(Context context) {
378 context.unregisterReceiver(this);
383 private DozeHost.Callback mHostCallback = new DozeHost.Callback() {
385 public void onNotificationHeadsUp() {
390 public void onPowerSaveChanged(boolean active) {
392 mMachine.requestState(DozeMachine.State.FINISH);