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.annotation.AnyThread;
20 import android.app.ActivityManager;
21 import android.app.AlarmManager;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.database.ContentObserver;
25 import android.hardware.Sensor;
26 import android.hardware.SensorEvent;
27 import android.hardware.SensorEventListener;
28 import android.hardware.SensorManager;
29 import android.hardware.TriggerEvent;
30 import android.hardware.TriggerEventListener;
31 import android.net.Uri;
32 import android.os.Handler;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.provider.Settings;
36 import android.text.TextUtils;
37 import android.util.Log;
39 import com.android.internal.hardware.AmbientDisplayConfiguration;
40 import com.android.internal.logging.MetricsLogger;
41 import com.android.internal.logging.nano.MetricsProto;
42 import com.android.systemui.statusbar.phone.DozeParameters;
43 import com.android.systemui.util.AlarmTimeout;
44 import com.android.systemui.util.wakelock.WakeLock;
46 import java.io.PrintWriter;
47 import java.util.List;
48 import java.util.function.Consumer;
50 public class DozeSensors {
52 private static final boolean DEBUG = DozeService.DEBUG;
54 private static final String TAG = "DozeSensors";
56 private final Context mContext;
57 private final AlarmManager mAlarmManager;
58 private final SensorManager mSensorManager;
59 private final TriggerSensor[] mSensors;
60 private final ContentResolver mResolver;
61 private final TriggerSensor mPickupSensor;
62 private final DozeParameters mDozeParameters;
63 private final AmbientDisplayConfiguration mConfig;
64 private final WakeLock mWakeLock;
65 private final Consumer<Boolean> mProxCallback;
66 private final Callback mCallback;
68 private final Handler mHandler = new Handler();
69 private final ProxSensor mProxSensor;
72 public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
73 DozeParameters dozeParameters,
74 AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback,
75 Consumer<Boolean> proxCallback) {
77 mAlarmManager = alarmManager;
78 mSensorManager = sensorManager;
79 mDozeParameters = dozeParameters;
82 mProxCallback = proxCallback;
83 mResolver = mContext.getContentResolver();
85 mSensors = new TriggerSensor[] {
87 mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
89 dozeParameters.getPulseOnSigMotion(),
90 DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */),
91 mPickupSensor = new TriggerSensor(
92 mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
93 Settings.Secure.DOZE_PULSE_ON_PICK_UP,
94 config.pulseOnPickupAvailable(),
95 DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */),
97 findSensorWithType(config.doubleTapSensorType()),
98 Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
99 true /* configured */,
100 DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
101 dozeParameters.doubleTapReportsTouchCoordinates()),
103 findSensorWithType(config.longPressSensorType()),
104 Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
105 false /* settingDef */,
106 true /* configured */,
107 DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
108 true /* reports touch coordinates */),
111 mProxSensor = new ProxSensor();
112 mCallback = callback;
115 private Sensor findSensorWithType(String type) {
116 return findSensorWithType(mSensorManager, type);
119 static Sensor findSensorWithType(SensorManager sensorManager, String type) {
120 if (TextUtils.isEmpty(type)) {
123 List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
124 for (Sensor s : sensorList) {
125 if (type.equals(s.getStringType())) {
132 public void setListening(boolean listen) {
133 for (TriggerSensor s : mSensors) {
134 s.setListening(listen);
136 s.registerSettingsObserver(mSettingsObserver);
140 mResolver.unregisterContentObserver(mSettingsObserver);
144 public void reregisterAllSensors() {
145 for (TriggerSensor s : mSensors) {
146 s.setListening(false);
148 for (TriggerSensor s : mSensors) {
149 s.setListening(true);
153 public void onUserSwitched() {
154 for (TriggerSensor s : mSensors) {
159 public void setProxListening(boolean listen) {
160 mProxSensor.setRequested(listen);
163 private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
165 public void onChange(boolean selfChange, Uri uri, int userId) {
166 if (userId != ActivityManager.getCurrentUser()) {
169 for (TriggerSensor s : mSensors) {
175 public void setDisableSensorsInterferingWithProximity(boolean disable) {
176 mPickupSensor.setDisabled(disable);
179 /** Dump current state */
180 public void dump(PrintWriter pw) {
181 for (TriggerSensor s : mSensors) {
182 pw.print("Sensor: "); pw.println(s.toString());
184 pw.print("ProxSensor: "); pw.println(mProxSensor.toString());
188 * @return true if prox is currently far, false if near or null if unknown.
190 public Boolean isProximityCurrentlyFar() {
191 return mProxSensor.mCurrentlyFar;
194 private class ProxSensor implements SensorEventListener {
196 static final long COOLDOWN_TRIGGER = 2 * 1000;
197 static final long COOLDOWN_PERIOD = 5 * 1000;
201 Boolean mCurrentlyFar;
203 final AlarmTimeout mCooldownTimer;
206 public ProxSensor() {
207 mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
208 "prox_cooldown", mHandler);
211 void setRequested(boolean requested) {
212 if (mRequested == requested) {
213 // Send an update even if we don't re-register.
214 mHandler.post(() -> {
215 if (mCurrentlyFar != null) {
216 mProxCallback.accept(mCurrentlyFar);
221 mRequested = requested;
225 private void updateRegistered() {
226 setRegistered(mRequested && !mCooldownTimer.isScheduled());
229 private void setRegistered(boolean register) {
230 if (mRegistered == register) {
234 mRegistered = mSensorManager.registerListener(this,
235 mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),
236 SensorManager.SENSOR_DELAY_NORMAL, mHandler);
238 mSensorManager.unregisterListener(this);
240 mCurrentlyFar = null;
245 public void onSensorChanged(SensorEvent event) {
246 mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
247 mProxCallback.accept(mCurrentlyFar);
249 long now = SystemClock.elapsedRealtime();
250 if (mCurrentlyFar == null) {
251 // Sensor has been unregistered by the proxCallback. Do nothing.
252 } else if (!mCurrentlyFar) {
254 } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) {
255 // If the last near was very recent, we might be using more power for prox
256 // wakeups than we're saving from turning of the screen. Instead, turn it off
258 mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
264 public void onAccuracyChanged(Sensor sensor, int accuracy) {
268 public String toString() {
269 return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}",
270 mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar);
274 private class TriggerSensor extends TriggerEventListener {
275 final Sensor mSensor;
276 final boolean mConfigured;
277 final int mPulseReason;
278 final String mSetting;
279 final boolean mReportsTouchCoordinates;
280 final boolean mSettingDefault;
282 private boolean mRequested;
283 private boolean mRegistered;
284 private boolean mDisabled;
286 public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
287 boolean reportsTouchCoordinates) {
288 this(sensor, setting, true /* settingDef */, configured, pulseReason,
289 reportsTouchCoordinates);
292 public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
293 boolean configured, int pulseReason, boolean reportsTouchCoordinates) {
296 mSettingDefault = settingDef;
297 mConfigured = configured;
298 mPulseReason = pulseReason;
299 mReportsTouchCoordinates = reportsTouchCoordinates;
302 public void setListening(boolean listen) {
303 if (mRequested == listen) return;
308 public void setDisabled(boolean disabled) {
309 if (mDisabled == disabled) return;
310 mDisabled = disabled;
314 public void updateListener() {
315 if (!mConfigured || mSensor == null) return;
316 if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
317 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
318 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
319 } else if (mRegistered) {
320 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
321 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
326 private boolean enabledBySetting() {
327 if (TextUtils.isEmpty(mSetting)) {
330 return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0,
331 UserHandle.USER_CURRENT) != 0;
335 public String toString() {
336 return new StringBuilder("{mRegistered=").append(mRegistered)
337 .append(", mRequested=").append(mRequested)
338 .append(", mDisabled=").append(mDisabled)
339 .append(", mConfigured=").append(mConfigured)
340 .append(", mSensor=").append(mSensor).append("}").toString();
345 public void onTrigger(TriggerEvent event) {
346 DozeLog.traceSensor(mContext, mPulseReason);
347 mHandler.post(mWakeLock.wrap(() -> {
348 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
349 boolean sensorPerformsProxCheck = false;
350 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
351 int subType = (int) event.values[0];
352 MetricsLogger.action(
353 mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE,
355 sensorPerformsProxCheck =
356 mDozeParameters.getPickupSubtypePerformsProxCheck(subType);
362 if (mReportsTouchCoordinates && event.values.length >= 2) {
363 screenX = event.values[0];
364 screenY = event.values[1];
366 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY);
367 updateListener(); // reregister, this sensor only fires once
371 public void registerSettingsObserver(ContentObserver settingsObserver) {
372 if (mConfigured && !TextUtils.isEmpty(mSetting)) {
373 mResolver.registerContentObserver(
374 Settings.Secure.getUriFor(mSetting), false /* descendants */,
375 mSettingsObserver, UserHandle.USER_ALL);
379 private String triggerEventToString(TriggerEvent event) {
380 if (event == null) return null;
381 final StringBuilder sb = new StringBuilder("TriggerEvent[")
382 .append(event.timestamp).append(',')
383 .append(event.sensor.getName());
384 if (event.values != null) {
385 for (int i = 0; i < event.values.length; i++) {
386 sb.append(',').append(event.values[i]);
389 return sb.append(']').toString();
393 public interface Callback {
396 * Called when a sensor requests a pulse
397 * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP}
398 * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity.
399 * @param screenX the location on the screen where the sensor fired or -1
400 * if the sensor doesn't support reporting screen locations.
401 * @param screenY the location on the screen where the sensor fired or -1
402 * if the sensor doesn't support reporting screen locations.
404 void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
405 float screenX, float screenY);