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 static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_DISPLAY;
20 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
21 
22 import android.annotation.AnyThread;
23 import android.app.ActivityManager;
24 import android.app.AlarmManager;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.database.ContentObserver;
28 import android.hardware.Sensor;
29 import android.hardware.SensorManager;
30 import android.hardware.TriggerEvent;
31 import android.hardware.TriggerEventListener;
32 import android.hardware.display.AmbientDisplayConfiguration;
33 import android.net.Uri;
34 import android.os.Handler;
35 import android.os.SystemClock;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.text.TextUtils;
39 import android.util.Log;
40 
41 import androidx.annotation.VisibleForTesting;
42 
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.UiEvent;
45 import com.android.internal.logging.UiEventLogger;
46 import com.android.internal.logging.UiEventLoggerImpl;
47 import com.android.internal.logging.nano.MetricsProto;
48 import com.android.systemui.plugins.SensorManagerPlugin;
49 import com.android.systemui.statusbar.phone.DozeParameters;
50 import com.android.systemui.util.sensors.AsyncSensorManager;
51 import com.android.systemui.util.sensors.ProximitySensor;
52 import com.android.systemui.util.wakelock.WakeLock;
53 
54 import java.io.PrintWriter;
55 import java.util.Collection;
56 import java.util.List;
57 import java.util.function.Consumer;
58 
59 public class DozeSensors {
60 
61     private static final boolean DEBUG = DozeService.DEBUG;
62     private static final String TAG = "DozeSensors";
63     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
64 
65     private final Context mContext;
66     private final AlarmManager mAlarmManager;
67     private final AsyncSensorManager mSensorManager;
68     private final ContentResolver mResolver;
69     private final TriggerSensor mPickupSensor;
70     private final DozeParameters mDozeParameters;
71     private final AmbientDisplayConfiguration mConfig;
72     private final WakeLock mWakeLock;
73     private final Consumer<Boolean> mProxCallback;
74     private final Callback mCallback;
75     @VisibleForTesting
76     protected TriggerSensor[] mSensors;
77 
78     private final Handler mHandler = new Handler();
79     private final ProximitySensor mProximitySensor;
80     private long mDebounceFrom;
81     private boolean mSettingRegistered;
82     private boolean mListening;
83     private boolean mPaused;
84 
85     @VisibleForTesting
86     public enum DozeSensorsUiEvent implements UiEventLogger.UiEventEnum {
87         @UiEvent(doc = "User performs pickup gesture that activates the ambient display")
88         ACTION_AMBIENT_GESTURE_PICKUP(459);
89 
90         private final int mId;
91 
DozeSensorsUiEvent(int id)92         DozeSensorsUiEvent(int id) {
93             mId = id;
94         }
95 
96         @Override
getId()97         public int getId() {
98             return mId;
99         }
100     }
101 
DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog, ProximitySensor proximitySensor)102     public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager,
103             DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
104             Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
105             ProximitySensor proximitySensor) {
106         mContext = context;
107         mAlarmManager = alarmManager;
108         mSensorManager = sensorManager;
109         mDozeParameters = dozeParameters;
110         mConfig = config;
111         mWakeLock = wakeLock;
112         mProxCallback = proxCallback;
113         mResolver = mContext.getContentResolver();
114         mCallback = callback;
115         mProximitySensor = proximitySensor;
116 
117         boolean alwaysOn = mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT);
118         mSensors = new TriggerSensor[] {
119                 new TriggerSensor(
120                         mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
121                         null /* setting */,
122                         dozeParameters.getPulseOnSigMotion(),
123                         DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */,
124                         false /* touchscreen */, dozeLog),
125                 mPickupSensor = new TriggerSensor(
126                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
127                         Settings.Secure.DOZE_PICK_UP_GESTURE,
128                         true /* settingDef */,
129                         config.dozePickupSensorAvailable(),
130                         DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
131                         false /* touchscreen */,
132                         false /* ignoresSetting */,
133                         dozeLog),
134                 new TriggerSensor(
135                         findSensorWithType(config.doubleTapSensorType()),
136                         Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
137                         true /* configured */,
138                         DozeLog.REASON_SENSOR_DOUBLE_TAP,
139                         dozeParameters.doubleTapReportsTouchCoordinates(),
140                         true /* touchscreen */,
141                         dozeLog),
142                 new TriggerSensor(
143                         findSensorWithType(config.tapSensorType()),
144                         Settings.Secure.DOZE_TAP_SCREEN_GESTURE,
145                         true /* configured */,
146                         DozeLog.REASON_SENSOR_TAP,
147                         false /* reports touch coordinates */,
148                         true /* touchscreen */,
149                         dozeLog),
150                 new TriggerSensor(
151                         findSensorWithType(config.longPressSensorType()),
152                         Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
153                         false /* settingDef */,
154                         true /* configured */,
155                         DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
156                         true /* reports touch coordinates */,
157                         true /* touchscreen */,
158                         dozeLog),
159                 new PluginSensor(
160                         new SensorManagerPlugin.Sensor(TYPE_WAKE_DISPLAY),
161                         Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
162                         mConfig.wakeScreenGestureAvailable() && alwaysOn,
163                         DozeLog.REASON_SENSOR_WAKE_UP,
164                         false /* reports touch coordinates */,
165                         false /* touchscreen */,
166                         dozeLog),
167                 new PluginSensor(
168                         new SensorManagerPlugin.Sensor(TYPE_WAKE_LOCK_SCREEN),
169                         Settings.Secure.DOZE_WAKE_LOCK_SCREEN_GESTURE,
170                         mConfig.wakeScreenGestureAvailable(),
171                         DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN,
172                         false /* reports touch coordinates */,
173                         false /* touchscreen */,
174                         mConfig.getWakeLockScreenDebounce(),
175                         dozeLog),
176         };
177 
178         setProxListening(false);  // Don't immediately start listening when we register.
179         mProximitySensor.register(
180                 proximityEvent -> {
181                     if (proximityEvent != null) {
182                         mProxCallback.accept(!proximityEvent.getNear());
183                     }
184                 });
185     }
186 
187     /**
188      *  Unregister any sensors.
189      */
destroy()190     public void destroy() {
191         // Unregisters everything, which is enough to allow gc.
192         for (TriggerSensor triggerSensor : mSensors) {
193             triggerSensor.setListening(false);
194         }
195         mProximitySensor.pause();
196     }
197 
198     /**
199      * Temporarily disable some sensors to avoid turning on the device while the user is
200      * turning it off.
201      */
requestTemporaryDisable()202     public void requestTemporaryDisable() {
203         mDebounceFrom = SystemClock.uptimeMillis();
204     }
205 
findSensorWithType(String type)206     private Sensor findSensorWithType(String type) {
207         return findSensorWithType(mSensorManager, type);
208     }
209 
findSensorWithType(SensorManager sensorManager, String type)210     static Sensor findSensorWithType(SensorManager sensorManager, String type) {
211         if (TextUtils.isEmpty(type)) {
212             return null;
213         }
214         List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
215         for (Sensor s : sensorList) {
216             if (type.equals(s.getStringType())) {
217                 return s;
218             }
219         }
220         return null;
221     }
222 
223     /**
224      * If sensors should be registered and sending signals.
225      */
setListening(boolean listen)226     public void setListening(boolean listen) {
227         if (mListening == listen) {
228             return;
229         }
230         mListening = listen;
231         updateListening();
232     }
233 
234     /**
235      * Unregister sensors, when listening, unless they are prox gated.
236      * @see #setListening(boolean)
237      */
setPaused(boolean paused)238     public void setPaused(boolean paused) {
239         if (mPaused == paused) {
240             return;
241         }
242         mPaused = paused;
243         updateListening();
244     }
245 
246     /**
247      * Registers/unregisters sensors based on internal state.
248      */
updateListening()249     public void updateListening() {
250         boolean anyListening = false;
251         for (TriggerSensor s : mSensors) {
252             s.setListening(mListening);
253             if (mListening) {
254                 anyListening = true;
255             }
256         }
257 
258         if (!anyListening) {
259             mResolver.unregisterContentObserver(mSettingsObserver);
260         } else if (!mSettingRegistered) {
261             for (TriggerSensor s : mSensors) {
262                 s.registerSettingsObserver(mSettingsObserver);
263             }
264         }
265         mSettingRegistered = anyListening;
266     }
267 
268     /** Set the listening state of only the sensors that require the touchscreen. */
setTouchscreenSensorsListening(boolean listening)269     public void setTouchscreenSensorsListening(boolean listening) {
270         for (TriggerSensor sensor : mSensors) {
271             if (sensor.mRequiresTouchscreen) {
272                 sensor.setListening(listening);
273             }
274         }
275     }
276 
onUserSwitched()277     public void onUserSwitched() {
278         for (TriggerSensor s : mSensors) {
279             s.updateListening();
280         }
281     }
282 
setProxListening(boolean listen)283     public void setProxListening(boolean listen) {
284         if (mProximitySensor.isRegistered() && listen) {
285             mProximitySensor.alertListeners();
286         } else {
287             if (listen) {
288                 mProximitySensor.resume();
289             } else {
290                 mProximitySensor.pause();
291             }
292         }
293     }
294 
295     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
296         @Override
297         public void onChange(boolean selfChange, Collection<Uri> uris, int flags, int userId) {
298             if (userId != ActivityManager.getCurrentUser()) {
299                 return;
300             }
301             for (TriggerSensor s : mSensors) {
302                 s.updateListening();
303             }
304         }
305     };
306 
setDisableSensorsInterferingWithProximity(boolean disable)307     public void setDisableSensorsInterferingWithProximity(boolean disable) {
308         mPickupSensor.setDisabled(disable);
309     }
310 
311     /** Ignore the setting value of only the sensors that require the touchscreen. */
ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore)312     public void ignoreTouchScreenSensorsSettingInterferingWithDocking(boolean ignore) {
313         for (TriggerSensor sensor : mSensors) {
314             if (sensor.mRequiresTouchscreen) {
315                 sensor.ignoreSetting(ignore);
316             }
317         }
318     }
319 
320     /** Dump current state */
dump(PrintWriter pw)321     public void dump(PrintWriter pw) {
322         for (TriggerSensor s : mSensors) {
323             pw.println("  Sensor: " + s.toString());
324         }
325         pw.println("  ProxSensor: " + mProximitySensor.toString());
326     }
327 
328     /**
329      * @return true if prox is currently near, false if far or null if unknown.
330      */
isProximityCurrentlyNear()331     public Boolean isProximityCurrentlyNear() {
332         return mProximitySensor.isNear();
333     }
334 
335     @VisibleForTesting
336     class TriggerSensor extends TriggerEventListener {
337         final Sensor mSensor;
338         final boolean mConfigured;
339         final int mPulseReason;
340         private final String mSetting;
341         private final boolean mReportsTouchCoordinates;
342         private final boolean mSettingDefault;
343         private final boolean mRequiresTouchscreen;
344 
345         protected boolean mRequested;
346         protected boolean mRegistered;
347         protected boolean mDisabled;
348         protected boolean mIgnoresSetting;
349         protected final DozeLog mDozeLog;
350 
TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog)351         public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
352                 boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog) {
353             this(sensor, setting, true /* settingDef */, configured, pulseReason,
354                     reportsTouchCoordinates, requiresTouchscreen, dozeLog);
355         }
356 
TriggerSensor(Sensor sensor, String setting, boolean settingDef, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog)357         public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
358                 boolean configured, int pulseReason, boolean reportsTouchCoordinates,
359                 boolean requiresTouchscreen, DozeLog dozeLog) {
360             this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
361                     requiresTouchscreen, false /* ignoresSetting */, dozeLog);
362         }
363 
TriggerSensor(Sensor sensor, String setting, boolean settingDef, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, boolean ignoresSetting, DozeLog dozeLog)364         private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
365                 boolean configured, int pulseReason, boolean reportsTouchCoordinates,
366                 boolean requiresTouchscreen, boolean ignoresSetting, DozeLog dozeLog) {
367             mSensor = sensor;
368             mSetting = setting;
369             mSettingDefault = settingDef;
370             mConfigured = configured;
371             mPulseReason = pulseReason;
372             mReportsTouchCoordinates = reportsTouchCoordinates;
373             mRequiresTouchscreen = requiresTouchscreen;
374             mIgnoresSetting = ignoresSetting;
375             mDozeLog = dozeLog;
376         }
377 
setListening(boolean listen)378         public void setListening(boolean listen) {
379             if (mRequested == listen) return;
380             mRequested = listen;
381             updateListening();
382         }
383 
setDisabled(boolean disabled)384         public void setDisabled(boolean disabled) {
385             if (mDisabled == disabled) return;
386             mDisabled = disabled;
387             updateListening();
388         }
389 
ignoreSetting(boolean ignored)390         public void ignoreSetting(boolean ignored) {
391             if (mIgnoresSetting == ignored) return;
392             mIgnoresSetting = ignored;
393             updateListening();
394         }
395 
updateListening()396         public void updateListening() {
397             if (!mConfigured || mSensor == null) return;
398             if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
399                     && !mRegistered) {
400                 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
401                 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
402             } else if (mRegistered) {
403                 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
404                 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
405                 mRegistered = false;
406             }
407         }
408 
enabledBySetting()409         protected boolean enabledBySetting() {
410             if (!mConfig.enabled(UserHandle.USER_CURRENT)) {
411                 return false;
412             } else if (TextUtils.isEmpty(mSetting)) {
413                 return true;
414             }
415             return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0,
416                     UserHandle.USER_CURRENT) != 0;
417         }
418 
419         @Override
toString()420         public String toString() {
421             return new StringBuilder("{mRegistered=").append(mRegistered)
422                     .append(", mRequested=").append(mRequested)
423                     .append(", mDisabled=").append(mDisabled)
424                     .append(", mConfigured=").append(mConfigured)
425                     .append(", mIgnoresSetting=").append(mIgnoresSetting)
426                     .append(", mSensor=").append(mSensor).append("}").toString();
427         }
428 
429         @Override
430         @AnyThread
onTrigger(TriggerEvent event)431         public void onTrigger(TriggerEvent event) {
432             mDozeLog.traceSensor(mPulseReason);
433             mHandler.post(mWakeLock.wrap(() -> {
434                 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
435                 if (mSensor != null && mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
436                     int subType = (int) event.values[0];
437                     MetricsLogger.action(
438                             mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE,
439                             subType);
440                     UI_EVENT_LOGGER.log(DozeSensorsUiEvent.ACTION_AMBIENT_GESTURE_PICKUP);
441                 }
442 
443                 mRegistered = false;
444                 float screenX = -1;
445                 float screenY = -1;
446                 if (mReportsTouchCoordinates && event.values.length >= 2) {
447                     screenX = event.values[0];
448                     screenY = event.values[1];
449                 }
450                 mCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
451                 if (!mRegistered) {
452                     updateListening();  // reregister, this sensor only fires once
453                 }
454             }));
455         }
456 
registerSettingsObserver(ContentObserver settingsObserver)457         public void registerSettingsObserver(ContentObserver settingsObserver) {
458             if (mConfigured && !TextUtils.isEmpty(mSetting)) {
459                 mResolver.registerContentObserver(
460                         Settings.Secure.getUriFor(mSetting), false /* descendants */,
461                         mSettingsObserver, UserHandle.USER_ALL);
462             }
463         }
464 
triggerEventToString(TriggerEvent event)465         protected String triggerEventToString(TriggerEvent event) {
466             if (event == null) return null;
467             final StringBuilder sb = new StringBuilder("SensorEvent[")
468                     .append(event.timestamp).append(',')
469                     .append(event.sensor.getName());
470             if (event.values != null) {
471                 for (int i = 0; i < event.values.length; i++) {
472                     sb.append(',').append(event.values[i]);
473                 }
474             }
475             return sb.append(']').toString();
476         }
477     }
478 
479     /**
480      * A Sensor that is injected via plugin.
481      */
482     @VisibleForTesting
483     class PluginSensor extends TriggerSensor implements SensorManagerPlugin.SensorEventListener {
484 
485         final SensorManagerPlugin.Sensor mPluginSensor;
486         private long mDebounce;
487 
PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, DozeLog dozeLog)488         PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
489                 int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
490                 DozeLog dozeLog) {
491             this(sensor, setting, configured, pulseReason, reportsTouchCoordinates,
492                     requiresTouchscreen, 0L /* debounce */, dozeLog);
493         }
494 
PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured, int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen, long debounce, DozeLog dozeLog)495         PluginSensor(SensorManagerPlugin.Sensor sensor, String setting, boolean configured,
496                 int pulseReason, boolean reportsTouchCoordinates, boolean requiresTouchscreen,
497                 long debounce, DozeLog dozeLog) {
498             super(null, setting, configured, pulseReason, reportsTouchCoordinates,
499                     requiresTouchscreen, dozeLog);
500             mPluginSensor = sensor;
501             mDebounce = debounce;
502         }
503 
504         @Override
updateListening()505         public void updateListening() {
506             if (!mConfigured) return;
507             AsyncSensorManager asyncSensorManager = (AsyncSensorManager) mSensorManager;
508             if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
509                     && !mRegistered) {
510                 asyncSensorManager.registerPluginListener(mPluginSensor, this);
511                 mRegistered = true;
512                 if (DEBUG) Log.d(TAG, "registerPluginListener");
513             } else if (mRegistered) {
514                 asyncSensorManager.unregisterPluginListener(mPluginSensor, this);
515                 mRegistered = false;
516                 if (DEBUG) Log.d(TAG, "unregisterPluginListener");
517             }
518         }
519 
520         @Override
toString()521         public String toString() {
522             return new StringBuilder("{mRegistered=").append(mRegistered)
523                     .append(", mRequested=").append(mRequested)
524                     .append(", mDisabled=").append(mDisabled)
525                     .append(", mConfigured=").append(mConfigured)
526                     .append(", mIgnoresSetting=").append(mIgnoresSetting)
527                     .append(", mSensor=").append(mPluginSensor).append("}").toString();
528         }
529 
triggerEventToString(SensorManagerPlugin.SensorEvent event)530         private String triggerEventToString(SensorManagerPlugin.SensorEvent event) {
531             if (event == null) return null;
532             final StringBuilder sb = new StringBuilder("PluginTriggerEvent[")
533                     .append(event.getSensor()).append(',')
534                     .append(event.getVendorType());
535             if (event.getValues() != null) {
536                 for (int i = 0; i < event.getValues().length; i++) {
537                     sb.append(',').append(event.getValues()[i]);
538                 }
539             }
540             return sb.append(']').toString();
541         }
542 
543         @Override
onSensorChanged(SensorManagerPlugin.SensorEvent event)544         public void onSensorChanged(SensorManagerPlugin.SensorEvent event) {
545             mDozeLog.traceSensor(mPulseReason);
546             mHandler.post(mWakeLock.wrap(() -> {
547                 final long now = SystemClock.uptimeMillis();
548                 if (now < mDebounceFrom + mDebounce) {
549                     Log.d(TAG, "onSensorEvent dropped: " + triggerEventToString(event));
550                     return;
551                 }
552                 if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
553                 mCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
554             }));
555         }
556     }
557 
558     public interface Callback {
559 
560         /**
561          * Called when a sensor requests a pulse
562          * @param pulseReason Requesting sensor, e.g. {@link DozeLog#REASON_SENSOR_PICKUP}
563          * @param screenX the location on the screen where the sensor fired or -1
564          *                if the sensor doesn't support reporting screen locations.
565          * @param screenY the location on the screen where the sensor fired or -1
566          * @param rawValues raw values array from the event.
567          */
onSensorPulse(int pulseReason, float screenX, float screenY, float[] rawValues)568         void onSensorPulse(int pulseReason, float screenX, float screenY, float[] rawValues);
569     }
570 }
571