DozeSensors.java revision d7aa26f33b70071a089bfcd7ae17e18d066501f0
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
17package com.android.systemui.doze;
18
19import android.annotation.AnyThread;
20import android.app.ActivityManager;
21import android.app.AlarmManager;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.database.ContentObserver;
25import android.hardware.Sensor;
26import android.hardware.SensorEvent;
27import android.hardware.SensorEventListener;
28import android.hardware.SensorManager;
29import android.hardware.TriggerEvent;
30import android.hardware.TriggerEventListener;
31import android.net.Uri;
32import android.os.Handler;
33import android.os.SystemClock;
34import android.os.UserHandle;
35import android.provider.Settings;
36import android.text.TextUtils;
37import android.util.Log;
38
39import com.android.internal.hardware.AmbientDisplayConfiguration;
40import com.android.internal.logging.MetricsLogger;
41import com.android.internal.logging.nano.MetricsProto;
42import com.android.systemui.statusbar.phone.DozeParameters;
43import com.android.systemui.util.AlarmTimeout;
44import com.android.systemui.util.wakelock.WakeLock;
45
46import java.io.PrintWriter;
47import java.util.List;
48import java.util.function.Consumer;
49
50public 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        };
103
104        mProxSensor = new ProxSensor();
105        mCallback = callback;
106    }
107
108    private Sensor findSensorWithType(String type) {
109        return findSensorWithType(mSensorManager, type);
110    }
111
112    static Sensor findSensorWithType(SensorManager sensorManager, String type) {
113        if (TextUtils.isEmpty(type)) {
114            return null;
115        }
116        List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
117        for (Sensor s : sensorList) {
118            if (type.equals(s.getStringType())) {
119                return s;
120            }
121        }
122        return null;
123    }
124
125    public void setListening(boolean listen) {
126        for (TriggerSensor s : mSensors) {
127            s.setListening(listen);
128            if (listen) {
129                s.registerSettingsObserver(mSettingsObserver);
130            }
131        }
132        if (!listen) {
133            mResolver.unregisterContentObserver(mSettingsObserver);
134        }
135    }
136
137    public void reregisterAllSensors() {
138        for (TriggerSensor s : mSensors) {
139            s.setListening(false);
140        }
141        for (TriggerSensor s : mSensors) {
142            s.setListening(true);
143        }
144    }
145
146    public void onUserSwitched() {
147        for (TriggerSensor s : mSensors) {
148            s.updateListener();
149        }
150    }
151
152    public void setProxListening(boolean listen) {
153        mProxSensor.setRequested(listen);
154    }
155
156    private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
157        @Override
158        public void onChange(boolean selfChange, Uri uri, int userId) {
159            if (userId != ActivityManager.getCurrentUser()) {
160                return;
161            }
162            for (TriggerSensor s : mSensors) {
163                s.updateListener();
164            }
165        }
166    };
167
168    public void setDisableSensorsInterferingWithProximity(boolean disable) {
169        mPickupSensor.setDisabled(disable);
170    }
171
172    /** Dump current state */
173    public void dump(PrintWriter pw) {
174        for (TriggerSensor s : mSensors) {
175            pw.print("Sensor: "); pw.println(s.toString());
176        }
177        pw.print("ProxSensor: "); pw.println(mProxSensor.toString());
178    }
179
180    private class ProxSensor implements SensorEventListener {
181
182        static final long COOLDOWN_TRIGGER = 2 * 1000;
183        static final long COOLDOWN_PERIOD = 5 * 1000;
184
185        boolean mRequested;
186        boolean mRegistered;
187        Boolean mCurrentlyFar;
188        long mLastNear;
189        final AlarmTimeout mCooldownTimer;
190
191
192        public ProxSensor() {
193            mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
194                    "prox_cooldown", mHandler);
195        }
196
197        void setRequested(boolean requested) {
198            if (mRequested == requested) {
199                // Send an update even if we don't re-register.
200                mHandler.post(() -> {
201                    if (mCurrentlyFar != null) {
202                        mProxCallback.accept(mCurrentlyFar);
203                    }
204                });
205                return;
206            }
207            mRequested = requested;
208            updateRegistered();
209        }
210
211        private void updateRegistered() {
212            setRegistered(mRequested && !mCooldownTimer.isScheduled());
213        }
214
215        private void setRegistered(boolean register) {
216            if (mRegistered == register) {
217                return;
218            }
219            if (register) {
220                mRegistered = mSensorManager.registerListener(this,
221                        mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),
222                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
223            } else {
224                mSensorManager.unregisterListener(this);
225                mRegistered = false;
226                mCurrentlyFar = null;
227            }
228        }
229
230        @Override
231        public void onSensorChanged(SensorEvent event) {
232            mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
233            mProxCallback.accept(mCurrentlyFar);
234
235            long now = SystemClock.elapsedRealtime();
236            if (mCurrentlyFar == null) {
237                // Sensor has been unregistered by the proxCallback. Do nothing.
238            } else if (!mCurrentlyFar) {
239                mLastNear = now;
240            } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) {
241                // If the last near was very recent, we might be using more power for prox
242                // wakeups than we're saving from turning of the screen. Instead, turn it off
243                // for a while.
244                mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
245                updateRegistered();
246            }
247        }
248
249        @Override
250        public void onAccuracyChanged(Sensor sensor, int accuracy) {
251        }
252
253        @Override
254        public String toString() {
255            return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}",
256                    mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar);
257        }
258    }
259
260    private class TriggerSensor extends TriggerEventListener {
261        final Sensor mSensor;
262        final boolean mConfigured;
263        final int mPulseReason;
264        final String mSetting;
265        final boolean mReportsTouchCoordinates;
266
267        private boolean mRequested;
268        private boolean mRegistered;
269        private boolean mDisabled;
270
271        public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
272                boolean reportsTouchCoordinates) {
273            mSensor = sensor;
274            mSetting = setting;
275            mConfigured = configured;
276            mPulseReason = pulseReason;
277            mReportsTouchCoordinates = reportsTouchCoordinates;
278        }
279
280        public void setListening(boolean listen) {
281            if (mRequested == listen) return;
282            mRequested = listen;
283            updateListener();
284        }
285
286        public void setDisabled(boolean disabled) {
287            if (mDisabled == disabled) return;
288            mDisabled = disabled;
289            updateListener();
290        }
291
292        public void updateListener() {
293            if (!mConfigured || mSensor == null) return;
294            if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
295                mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
296                if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
297            } else if (mRegistered) {
298                final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
299                if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
300                mRegistered = false;
301            }
302        }
303
304        private boolean enabledBySetting() {
305            if (TextUtils.isEmpty(mSetting)) {
306                return true;
307            }
308            return Settings.Secure.getIntForUser(mResolver, mSetting, 1,
309                    UserHandle.USER_CURRENT) != 0;
310        }
311
312        @Override
313        public String toString() {
314            return new StringBuilder("{mRegistered=").append(mRegistered)
315                    .append(", mRequested=").append(mRequested)
316                    .append(", mDisabled=").append(mDisabled)
317                    .append(", mConfigured=").append(mConfigured)
318                    .append(", mSensor=").append(mSensor).append("}").toString();
319        }
320
321        @Override
322        @AnyThread
323        public void onTrigger(TriggerEvent event) {
324            DozeLog.traceSensor(mContext, mPulseReason);
325            mHandler.post(mWakeLock.wrap(() -> {
326                if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
327                boolean sensorPerformsProxCheck = false;
328                if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
329                    int subType = (int) event.values[0];
330                    MetricsLogger.action(
331                            mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE,
332                            subType);
333                    sensorPerformsProxCheck =
334                            mDozeParameters.getPickupSubtypePerformsProxCheck(subType);
335                }
336
337                mRegistered = false;
338                float screenX = -1;
339                float screenY = -1;
340                if (mReportsTouchCoordinates && event.values.length >= 2) {
341                    screenX = event.values[0];
342                    screenY = event.values[1];
343                }
344                mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY);
345                updateListener();  // reregister, this sensor only fires once
346            }));
347        }
348
349        public void registerSettingsObserver(ContentObserver settingsObserver) {
350            if (mConfigured && !TextUtils.isEmpty(mSetting)) {
351                mResolver.registerContentObserver(
352                        Settings.Secure.getUriFor(mSetting), false /* descendants */,
353                        mSettingsObserver, UserHandle.USER_ALL);
354            }
355        }
356
357        private String triggerEventToString(TriggerEvent event) {
358            if (event == null) return null;
359            final StringBuilder sb = new StringBuilder("TriggerEvent[")
360                    .append(event.timestamp).append(',')
361                    .append(event.sensor.getName());
362            if (event.values != null) {
363                for (int i = 0; i < event.values.length; i++) {
364                    sb.append(',').append(event.values[i]);
365                }
366            }
367            return sb.append(']').toString();
368        }
369    }
370
371    public interface Callback {
372
373        /**
374         * Called when a sensor requests a pulse
375         * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP}
376         * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity.
377         * @param screenX the location on the screen where the sensor fired or -1
378         *                if the sensor doesn't support reporting screen locations.
379         * @param screenY the location on the screen where the sensor fired or -1
380         *                if the sensor doesn't support reporting screen locations.
381         */
382        void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
383                float screenX, float screenY);
384    }
385}
386