OSDN Git Service

bf1c06035ec20c6337718f401d35f6f1246f9e3e
[android-x86/frameworks-base.git] / packages / SystemUI / src / com / android / systemui / doze / DozeSensors.java
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.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;
38
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;
45
46 import java.io.PrintWriter;
47 import java.util.List;
48 import java.util.function.Consumer;
49
50 public class DozeSensors {
51
52     private static final boolean DEBUG = DozeService.DEBUG;
53
54     private static final String TAG = "DozeSensors";
55
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;
67
68     private final Handler mHandler = new Handler();
69     private final ProxSensor mProxSensor;
70
71
72     public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
73             DozeParameters dozeParameters,
74             AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback,
75             Consumer<Boolean> proxCallback) {
76         mContext = context;
77         mAlarmManager = alarmManager;
78         mSensorManager = sensorManager;
79         mDozeParameters = dozeParameters;
80         mConfig = config;
81         mWakeLock = wakeLock;
82         mProxCallback = proxCallback;
83         mResolver = mContext.getContentResolver();
84
85         mSensors = new TriggerSensor[] {
86                 new TriggerSensor(
87                         mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
88                         null /* setting */,
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 */),
96                 new TriggerSensor(
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()),
102                 new TriggerSensor(
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 */),
109         };
110
111         mProxSensor = new ProxSensor();
112         mCallback = callback;
113     }
114
115     private Sensor findSensorWithType(String type) {
116         return findSensorWithType(mSensorManager, type);
117     }
118
119     static Sensor findSensorWithType(SensorManager sensorManager, String type) {
120         if (TextUtils.isEmpty(type)) {
121             return null;
122         }
123         List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
124         for (Sensor s : sensorList) {
125             if (type.equals(s.getStringType())) {
126                 return s;
127             }
128         }
129         return null;
130     }
131
132     public void setListening(boolean listen) {
133         for (TriggerSensor s : mSensors) {
134             s.setListening(listen);
135             if (listen) {
136                 s.registerSettingsObserver(mSettingsObserver);
137             }
138         }
139         if (!listen) {
140             mResolver.unregisterContentObserver(mSettingsObserver);
141         }
142     }
143
144     public void reregisterAllSensors() {
145         for (TriggerSensor s : mSensors) {
146             s.setListening(false);
147         }
148         for (TriggerSensor s : mSensors) {
149             s.setListening(true);
150         }
151     }
152
153     public void onUserSwitched() {
154         for (TriggerSensor s : mSensors) {
155             s.updateListener();
156         }
157     }
158
159     public void setProxListening(boolean listen) {
160         mProxSensor.setRequested(listen);
161     }
162
163     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
164         @Override
165         public void onChange(boolean selfChange, Uri uri, int userId) {
166             if (userId != ActivityManager.getCurrentUser()) {
167                 return;
168             }
169             for (TriggerSensor s : mSensors) {
170                 s.updateListener();
171             }
172         }
173     };
174
175     public void setDisableSensorsInterferingWithProximity(boolean disable) {
176         mPickupSensor.setDisabled(disable);
177     }
178
179     /** Dump current state */
180     public void dump(PrintWriter pw) {
181         for (TriggerSensor s : mSensors) {
182             pw.print("Sensor: "); pw.println(s.toString());
183         }
184         pw.print("ProxSensor: "); pw.println(mProxSensor.toString());
185     }
186
187     /**
188      * @return true if prox is currently far, false if near or null if unknown.
189      */
190     public Boolean isProximityCurrentlyFar() {
191         return mProxSensor.mCurrentlyFar;
192     }
193
194     private class ProxSensor implements SensorEventListener {
195
196         static final long COOLDOWN_TRIGGER = 2 * 1000;
197         static final long COOLDOWN_PERIOD = 5 * 1000;
198
199         boolean mRequested;
200         boolean mRegistered;
201         Boolean mCurrentlyFar;
202         long mLastNear;
203         final AlarmTimeout mCooldownTimer;
204
205
206         public ProxSensor() {
207             mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
208                     "prox_cooldown", mHandler);
209         }
210
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);
217                     }
218                 });
219                 return;
220             }
221             mRequested = requested;
222             updateRegistered();
223         }
224
225         private void updateRegistered() {
226             setRegistered(mRequested && !mCooldownTimer.isScheduled());
227         }
228
229         private void setRegistered(boolean register) {
230             if (mRegistered == register) {
231                 return;
232             }
233             if (register) {
234                 mRegistered = mSensorManager.registerListener(this,
235                         mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),
236                         SensorManager.SENSOR_DELAY_NORMAL, mHandler);
237             } else {
238                 mSensorManager.unregisterListener(this);
239                 mRegistered = false;
240                 mCurrentlyFar = null;
241             }
242         }
243
244         @Override
245         public void onSensorChanged(SensorEvent event) {
246             mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
247             mProxCallback.accept(mCurrentlyFar);
248
249             long now = SystemClock.elapsedRealtime();
250             if (mCurrentlyFar == null) {
251                 // Sensor has been unregistered by the proxCallback. Do nothing.
252             } else if (!mCurrentlyFar) {
253                 mLastNear = now;
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
257                 // for a while.
258                 mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
259                 updateRegistered();
260             }
261         }
262
263         @Override
264         public void onAccuracyChanged(Sensor sensor, int accuracy) {
265         }
266
267         @Override
268         public String toString() {
269             return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}",
270                     mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar);
271         }
272     }
273
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;
281
282         private boolean mRequested;
283         private boolean mRegistered;
284         private boolean mDisabled;
285
286         public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
287                 boolean reportsTouchCoordinates) {
288             this(sensor, setting, true /* settingDef */, configured, pulseReason,
289                     reportsTouchCoordinates);
290         }
291
292         public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
293                 boolean configured, int pulseReason, boolean reportsTouchCoordinates) {
294             mSensor = sensor;
295             mSetting = setting;
296             mSettingDefault = settingDef;
297             mConfigured = configured;
298             mPulseReason = pulseReason;
299             mReportsTouchCoordinates = reportsTouchCoordinates;
300         }
301
302         public void setListening(boolean listen) {
303             if (mRequested == listen) return;
304             mRequested = listen;
305             updateListener();
306         }
307
308         public void setDisabled(boolean disabled) {
309             if (mDisabled == disabled) return;
310             mDisabled = disabled;
311             updateListener();
312         }
313
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);
322                 mRegistered = false;
323             }
324         }
325
326         private boolean enabledBySetting() {
327             if (TextUtils.isEmpty(mSetting)) {
328                 return true;
329             }
330             return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0,
331                     UserHandle.USER_CURRENT) != 0;
332         }
333
334         @Override
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();
341         }
342
343         @Override
344         @AnyThread
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,
354                             subType);
355                     sensorPerformsProxCheck =
356                             mDozeParameters.getPickupSubtypePerformsProxCheck(subType);
357                 }
358
359                 mRegistered = false;
360                 float screenX = -1;
361                 float screenY = -1;
362                 if (mReportsTouchCoordinates && event.values.length >= 2) {
363                     screenX = event.values[0];
364                     screenY = event.values[1];
365                 }
366                 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY);
367                 updateListener();  // reregister, this sensor only fires once
368             }));
369         }
370
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);
376             }
377         }
378
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]);
387                 }
388             }
389             return sb.append(']').toString();
390         }
391     }
392
393     public interface Callback {
394
395         /**
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.
403          */
404         void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
405                 float screenX, float screenY);
406     }
407 }