DozeSensors.java revision 2981eb0d59f3568bfe84ce905c82fc17d62d21c5
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.content.ContentResolver;
22import android.content.Context;
23import android.database.ContentObserver;
24import android.hardware.Sensor;
25import android.hardware.SensorEvent;
26import android.hardware.SensorEventListener;
27import android.hardware.SensorManager;
28import android.hardware.TriggerEvent;
29import android.hardware.TriggerEventListener;
30import android.net.Uri;
31import android.os.Handler;
32import android.os.UserHandle;
33import android.provider.Settings;
34import android.text.TextUtils;
35import android.util.Log;
36
37import com.android.internal.hardware.AmbientDisplayConfiguration;
38import com.android.internal.logging.MetricsLogger;
39import com.android.internal.logging.nano.MetricsProto;
40import com.android.systemui.statusbar.phone.DozeParameters;
41import com.android.systemui.util.wakelock.WakeLock;
42
43import java.io.PrintWriter;
44import java.util.List;
45import java.util.function.Consumer;
46
47public class DozeSensors {
48
49    private static final boolean DEBUG = DozeService.DEBUG;
50
51    private static final String TAG = "DozeSensors";
52
53    private final Context mContext;
54    private final SensorManager mSensorManager;
55    private final TriggerSensor[] mSensors;
56    private final ContentResolver mResolver;
57    private final TriggerSensor mPickupSensor;
58    private final DozeParameters mDozeParameters;
59    private final AmbientDisplayConfiguration mConfig;
60    private final WakeLock mWakeLock;
61    private final Consumer<Boolean> mProxCallback;
62    private final Callback mCallback;
63
64    private final Handler mHandler = new Handler();
65    private final ProxSensor mProxSensor;
66
67
68    public DozeSensors(Context context, SensorManager sensorManager, DozeParameters dozeParameters,
69            AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback,
70            Consumer<Boolean> proxCallback) {
71        mContext = context;
72        mSensorManager = sensorManager;
73        mDozeParameters = dozeParameters;
74        mConfig = config;
75        mWakeLock = wakeLock;
76        mProxCallback = proxCallback;
77        mResolver = mContext.getContentResolver();
78
79        mSensors = new TriggerSensor[] {
80                new TriggerSensor(
81                        mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
82                        null /* setting */,
83                        dozeParameters.getPulseOnSigMotion(),
84                        DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */),
85                mPickupSensor = new TriggerSensor(
86                        mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
87                        Settings.Secure.DOZE_PULSE_ON_PICK_UP,
88                        config.pulseOnPickupAvailable(),
89                        DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */),
90                new TriggerSensor(
91                        findSensorWithType(config.doubleTapSensorType()),
92                        Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
93                        true /* configured */,
94                        DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
95                        dozeParameters.doubleTapReportsTouchCoordinates())
96        };
97
98        mProxSensor = new ProxSensor();
99        mCallback = callback;
100    }
101
102    private Sensor findSensorWithType(String type) {
103        return findSensorWithType(mSensorManager, type);
104    }
105
106    static Sensor findSensorWithType(SensorManager sensorManager, String type) {
107        if (TextUtils.isEmpty(type)) {
108            return null;
109        }
110        List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
111        for (Sensor s : sensorList) {
112            if (type.equals(s.getStringType())) {
113                return s;
114            }
115        }
116        return null;
117    }
118
119    public void setListening(boolean listen) {
120        for (TriggerSensor s : mSensors) {
121            s.setListening(listen);
122            if (listen) {
123                s.registerSettingsObserver(mSettingsObserver);
124            }
125        }
126        if (!listen) {
127            mResolver.unregisterContentObserver(mSettingsObserver);
128        }
129    }
130
131    public void reregisterAllSensors() {
132        for (TriggerSensor s : mSensors) {
133            s.setListening(false);
134        }
135        for (TriggerSensor s : mSensors) {
136            s.setListening(true);
137        }
138    }
139
140    public void onUserSwitched() {
141        for (TriggerSensor s : mSensors) {
142            s.updateListener();
143        }
144    }
145
146    public void setProxListening(boolean listen) {
147        mProxSensor.setRegistered(listen);
148    }
149
150    private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
151        @Override
152        public void onChange(boolean selfChange, Uri uri, int userId) {
153            if (userId != ActivityManager.getCurrentUser()) {
154                return;
155            }
156            for (TriggerSensor s : mSensors) {
157                s.updateListener();
158            }
159        }
160    };
161
162    public void setDisableSensorsInterferingWithProximity(boolean disable) {
163        mPickupSensor.setDisabled(disable);
164    }
165
166    /** Dump current state */
167    public void dump(PrintWriter pw) {
168        for (TriggerSensor s : mSensors) {
169            pw.print("Sensor: "); pw.println(s.toString());
170        }
171    }
172
173    private class ProxSensor implements SensorEventListener {
174
175        boolean mRegistered;
176        Boolean mCurrentlyFar;
177
178        void setRegistered(boolean register) {
179            if (mRegistered == register) {
180                // Send an update even if we don't re-register.
181                mHandler.post(() -> {
182                    if (mCurrentlyFar != null) {
183                        mProxCallback.accept(mCurrentlyFar);
184                    }
185                });
186                return;
187            }
188            if (register) {
189                mRegistered = mSensorManager.registerListener(this,
190                        mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),
191                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
192            } else {
193                mSensorManager.unregisterListener(this);
194                mRegistered = false;
195                mCurrentlyFar = null;
196            }
197        }
198
199        @Override
200        public void onSensorChanged(SensorEvent event) {
201            mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
202            mProxCallback.accept(mCurrentlyFar);
203        }
204
205        @Override
206        public void onAccuracyChanged(Sensor sensor, int accuracy) {
207        }
208    }
209
210    private class TriggerSensor extends TriggerEventListener {
211        final Sensor mSensor;
212        final boolean mConfigured;
213        final int mPulseReason;
214        final String mSetting;
215        final boolean mReportsTouchCoordinates;
216
217        private boolean mRequested;
218        private boolean mRegistered;
219        private boolean mDisabled;
220
221        public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
222                boolean reportsTouchCoordinates) {
223            mSensor = sensor;
224            mSetting = setting;
225            mConfigured = configured;
226            mPulseReason = pulseReason;
227            mReportsTouchCoordinates = reportsTouchCoordinates;
228        }
229
230        public void setListening(boolean listen) {
231            if (mRequested == listen) return;
232            mRequested = listen;
233            updateListener();
234        }
235
236        public void setDisabled(boolean disabled) {
237            if (mDisabled == disabled) return;
238            mDisabled = disabled;
239            updateListener();
240        }
241
242        public void updateListener() {
243            if (!mConfigured || mSensor == null) return;
244            if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
245                mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
246                if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
247            } else if (mRegistered) {
248                final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
249                if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
250                mRegistered = false;
251            }
252        }
253
254        private boolean enabledBySetting() {
255            if (TextUtils.isEmpty(mSetting)) {
256                return true;
257            }
258            return Settings.Secure.getIntForUser(mResolver, mSetting, 1,
259                    UserHandle.USER_CURRENT) != 0;
260        }
261
262        @Override
263        public String toString() {
264            return new StringBuilder("{mRegistered=").append(mRegistered)
265                    .append(", mRequested=").append(mRequested)
266                    .append(", mDisabled=").append(mDisabled)
267                    .append(", mConfigured=").append(mConfigured)
268                    .append(", mSensor=").append(mSensor).append("}").toString();
269        }
270
271        @Override
272        @AnyThread
273        public void onTrigger(TriggerEvent event) {
274            mHandler.post(mWakeLock.wrap(() -> {
275                if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
276                boolean sensorPerformsProxCheck = false;
277                if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
278                    int subType = (int) event.values[0];
279                    MetricsLogger.action(
280                            mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE,
281                            subType);
282                    sensorPerformsProxCheck =
283                            mDozeParameters.getPickupSubtypePerformsProxCheck(subType);
284                }
285
286                mRegistered = false;
287                float screenX = -1;
288                float screenY = -1;
289                if (mReportsTouchCoordinates && event.values.length >= 2) {
290                    screenX = event.values[0];
291                    screenY = event.values[1];
292                }
293                mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY);
294                updateListener();  // reregister, this sensor only fires once
295            }));
296        }
297
298        public void registerSettingsObserver(ContentObserver settingsObserver) {
299            if (mConfigured && !TextUtils.isEmpty(mSetting)) {
300                mResolver.registerContentObserver(
301                        Settings.Secure.getUriFor(mSetting), false /* descendants */,
302                        mSettingsObserver, UserHandle.USER_ALL);
303            }
304        }
305
306        private String triggerEventToString(TriggerEvent event) {
307            if (event == null) return null;
308            final StringBuilder sb = new StringBuilder("TriggerEvent[")
309                    .append(event.timestamp).append(',')
310                    .append(event.sensor.getName());
311            if (event.values != null) {
312                for (int i = 0; i < event.values.length; i++) {
313                    sb.append(',').append(event.values[i]);
314                }
315            }
316            return sb.append(']').toString();
317        }
318    }
319
320    public interface Callback {
321
322        /**
323         * Called when a sensor requests a pulse
324         * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP}
325         * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity.
326         * @param screenX the location on the screen where the sensor fired or -1
327         *                if the sensor doesn't support reporting screen locations.
328         * @param screenY the location on the screen where the sensor fired or -1
329         *                if the sensor doesn't support reporting screen locations.
330         */
331        void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
332                float screenX, float screenY);
333    }
334}
335