1 /*
2  * Copyright (C) 2020 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.util.sensors;
18 
19 import android.content.res.Resources;
20 import android.hardware.Sensor;
21 import android.hardware.SensorEvent;
22 import android.hardware.SensorEventListener;
23 import android.hardware.SensorManager;
24 import android.text.TextUtils;
25 import android.util.Log;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.systemui.dagger.qualifiers.Main;
29 import com.android.systemui.util.concurrency.Execution;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 
34 import javax.inject.Inject;
35 
36 /**
37  * Sensor that will only trigger beyond some lower and upper threshold.
38  */
39 public class ThresholdSensorImpl implements ThresholdSensor {
40     private static final String TAG = "ThresholdSensor";
41     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
42 
43     private final AsyncSensorManager mSensorManager;
44     private final Execution mExecution;
45     private final Sensor mSensor;
46     private final float mThreshold;
47     private boolean mRegistered;
48     private boolean mPaused;
49     private List<Listener> mListeners = new ArrayList<>();
50     private Boolean mLastBelow;
51     private String mTag;
52     private final float mThresholdLatch;
53     private int mSensorDelay;
54 
55     private SensorEventListener mSensorEventListener = new SensorEventListener() {
56         @Override
57         public void onSensorChanged(SensorEvent event) {
58             boolean below = event.values[0] < mThreshold;
59             boolean above = event.values[0] >= mThresholdLatch;
60             logDebug("Sensor value: " + event.values[0]);
61             onSensorEvent(below, above, event.timestamp);
62         }
63 
64         @Override
65         public void onAccuracyChanged(Sensor sensor, int accuracy) {
66         }
67     };
68 
ThresholdSensorImpl(AsyncSensorManager sensorManager, Sensor sensor, Execution execution, float threshold, float thresholdLatch, int sensorDelay)69     private ThresholdSensorImpl(AsyncSensorManager sensorManager, Sensor sensor,
70             Execution execution,  float threshold, float thresholdLatch, int sensorDelay) {
71         mSensorManager = sensorManager;
72         mExecution = execution;
73         mSensor = sensor;
74         mThreshold = threshold;
75         mThresholdLatch = thresholdLatch;
76         mSensorDelay = sensorDelay;
77     }
78 
79     @Override
setTag(String tag)80     public void setTag(String tag) {
81         mTag = tag;
82     }
83 
84     @Override
setDelay(int delay)85     public void setDelay(int delay) {
86         if (delay == mSensorDelay) {
87             return;
88         }
89 
90         mSensorDelay = delay;
91         if (isLoaded()) {
92             unregisterInternal();
93             registerInternal();
94         }
95     }
96 
97     @Override
isLoaded()98     public boolean isLoaded() {
99         return mSensor != null;
100     }
101 
102     @VisibleForTesting
isRegistered()103     boolean isRegistered() {
104         return mRegistered;
105     }
106 
107     /**
108      * Registers the listener with the sensor.
109      *
110      * Multiple listeners are not supported at this time.
111      *
112      * Returns true if the listener was successfully registered. False otherwise.
113      */
114     @Override
register(Listener listener)115     public void register(Listener listener) {
116         mExecution.assertIsMainThread();
117         if (!mListeners.contains(listener)) {
118             mListeners.add(listener);
119         }
120         registerInternal();
121     }
122 
123     @Override
unregister(Listener listener)124     public void unregister(Listener listener) {
125         mExecution.assertIsMainThread();
126         mListeners.remove(listener);
127         unregisterInternal();
128     }
129 
130     /**
131      * Unregister with the {@link SensorManager} without unsetting listeners on this object.
132      */
133     @Override
pause()134     public void pause() {
135         mExecution.assertIsMainThread();
136         mPaused = true;
137         unregisterInternal();
138     }
139 
140     /**
141      * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
142      */
143     @Override
resume()144     public void resume() {
145         mExecution.assertIsMainThread();
146         mPaused = false;
147         registerInternal();
148     }
149 
alertListenersInternal(boolean below, long timestampNs)150     private void alertListenersInternal(boolean below, long timestampNs) {
151         List<Listener> listeners = new ArrayList<>(mListeners);
152         listeners.forEach(listener ->
153                 listener.onThresholdCrossed(new ThresholdSensorEvent(below, timestampNs)));
154     }
155 
registerInternal()156     private void registerInternal() {
157         mExecution.assertIsMainThread();
158         if (mRegistered || mPaused || mListeners.isEmpty()) {
159             return;
160         }
161         logDebug("Registering sensor listener");
162         mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay);
163         mRegistered = true;
164     }
165 
unregisterInternal()166     private void unregisterInternal() {
167         mExecution.assertIsMainThread();
168         if (!mRegistered) {
169             return;
170         }
171         logDebug("Unregister sensor listener");
172         mSensorManager.unregisterListener(mSensorEventListener);
173         mRegistered = false;
174         mLastBelow = null;  // Forget what we know.
175     }
176 
177     /**
178      * Call when the sensor reports a new value.
179      *
180      * Separate below-threshold and above-thresholds are specified. this allows latching behavior,
181      * where a different threshold can be specified for triggering the sensor depending on if it's
182      * going from above to below or below to above. To outside listeners of this class, the class
183      * still appears entirely binary.
184      */
onSensorEvent(boolean belowThreshold, boolean aboveThreshold, long timestampNs)185     private void onSensorEvent(boolean belowThreshold, boolean aboveThreshold, long timestampNs) {
186         mExecution.assertIsMainThread();
187         if (!mRegistered) {
188             return;
189         }
190         if (mLastBelow != null) {
191             // If we last reported below and are not yet above, change nothing.
192             if (mLastBelow && !aboveThreshold) {
193                 return;
194             }
195             // If we last reported above and are not yet below, change nothing.
196             if (!mLastBelow && !belowThreshold) {
197                 return;
198             }
199         }
200         mLastBelow = belowThreshold;
201         logDebug("Alerting below: " + belowThreshold);
202         alertListenersInternal(belowThreshold, timestampNs);
203     }
204 
205     @Override
getName()206     public String getName() {
207         return mSensor != null ? mSensor.getName() : null;
208     }
209 
210     @Override
getType()211     public String getType() {
212         return mSensor != null ? mSensor.getStringType() : null;
213     }
214 
215     @Override
toString()216     public String toString() {
217         return String.format("{isLoaded=%s, registered=%s, paused=%s, threshold=%s, sensor=%s}",
218                 isLoaded(), mRegistered, mPaused, mThreshold, mSensor);
219     }
220 
logDebug(String msg)221     private void logDebug(String msg) {
222         if (DEBUG) {
223             Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
224         }
225     }
226 
227     /**
228      * Use to build a ThresholdSensor. Should only be used once per sensor built, since
229      * parameters are not reset after calls to build(). For ease of retrievingnew Builders, use
230      * {@link BuilderFactory}.
231      */
232     public static class Builder {
233         private final Resources mResources;
234         private final AsyncSensorManager mSensorManager;
235         private final Execution mExecution;
236         private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;;
237         private float mThresholdValue;
238         private float mThresholdLatchValue;
239         private Sensor mSensor;
240         private boolean mSensorSet;
241         private boolean mThresholdSet;
242         private boolean mThresholdLatchValueSet;
243 
244         @Inject
Builder(@ain Resources resources, AsyncSensorManager sensorManager, Execution execution)245         Builder(@Main Resources resources, AsyncSensorManager sensorManager, Execution execution) {
246             mResources = resources;
247             mSensorManager = sensorManager;
248             mExecution = execution;
249         }
250 
setSensorDelay(int sensorDelay)251         Builder setSensorDelay(int sensorDelay) {
252             mSensorDelay = sensorDelay;
253             return this;
254         }
255         /**
256          * If requiresWakeUp is false, the first sensor with sensorType (regardless of whether the
257          * sensor is a wakeup sensor or not) will be set.
258          */
setSensorResourceId(int sensorResourceId, boolean requireWakeUp)259         Builder setSensorResourceId(int sensorResourceId, boolean requireWakeUp) {
260             setSensorType(mResources.getString(sensorResourceId), requireWakeUp);
261             return this;
262         }
263 
setThresholdResourceId(int thresholdResourceId)264         Builder setThresholdResourceId(int thresholdResourceId) {
265             try {
266                 setThresholdValue(mResources.getFloat(thresholdResourceId));
267             } catch (Resources.NotFoundException e) {
268                 // no-op
269             }
270             return this;
271         }
272 
setThresholdLatchResourceId(int thresholdLatchResourceId)273         Builder setThresholdLatchResourceId(int thresholdLatchResourceId) {
274             try {
275                 setThresholdLatchValue(mResources.getFloat(thresholdLatchResourceId));
276             } catch (Resources.NotFoundException e) {
277                 // no-op
278             }
279             return this;
280         }
281 
282         /**
283          * If requiresWakeUp is false, the first sensor with sensorType (regardless of whether the
284          * sensor is a wakeup sensor or not) will be set.
285          */
setSensorType(String sensorType, boolean requireWakeUp)286         Builder setSensorType(String sensorType, boolean requireWakeUp) {
287             Sensor sensor = findSensorByType(sensorType, requireWakeUp);
288             if (sensor != null) {
289                 setSensor(sensor);
290             }
291             return this;
292         }
293 
setThresholdValue(float thresholdValue)294         Builder setThresholdValue(float thresholdValue) {
295             mThresholdValue = thresholdValue;
296             mThresholdSet = true;
297             if (!mThresholdLatchValueSet) {
298                 mThresholdLatchValue = mThresholdValue;
299             }
300             return this;
301         }
302 
setThresholdLatchValue(float thresholdLatchValue)303         Builder setThresholdLatchValue(float thresholdLatchValue) {
304             mThresholdLatchValue = thresholdLatchValue;
305             mThresholdLatchValueSet = true;
306             return this;
307         }
308 
setSensor(Sensor sensor)309         Builder setSensor(Sensor sensor) {
310             mSensor = sensor;
311             mSensorSet = true;
312             return this;
313         }
314 
315         /**
316          * Creates a {@link ThresholdSensor} backed by a {@link ThresholdSensorImpl}.
317          */
build()318         public ThresholdSensor build() {
319             if (!mSensorSet) {
320                 throw new IllegalStateException("A sensor was not successfully set.");
321             }
322 
323             if (!mThresholdSet) {
324                 throw new IllegalStateException("A threshold was not successfully set.");
325             }
326 
327             if (mThresholdValue > mThresholdLatchValue) {
328                 throw new IllegalStateException(
329                         "Threshold must be less than or equal to Threshold Latch");
330             }
331 
332             return new ThresholdSensorImpl(
333                     mSensorManager, mSensor, mExecution,
334                     mThresholdValue, mThresholdLatchValue, mSensorDelay);
335         }
336 
337         @VisibleForTesting
findSensorByType(String sensorType, boolean requireWakeUp)338         Sensor findSensorByType(String sensorType, boolean requireWakeUp) {
339             if (TextUtils.isEmpty(sensorType)) {
340                 return null;
341             }
342 
343             List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
344             Sensor sensor = null;
345             for (Sensor s : sensorList) {
346                 if (sensorType.equals(s.getStringType())) {
347                     sensor = s;
348                     if (!requireWakeUp || sensor.isWakeUpSensor()) {
349                         break;
350                     }
351                 }
352             }
353 
354             return sensor;
355         }
356     }
357 
358     /**
359      * Factory that creates a new ThresholdSensorImpl.Builder. In general, Builders should not be
360      * reused after creating a ThresholdSensor or else there may be default threshold and sensor
361      * values set from the previous built sensor.
362      */
363     public static class BuilderFactory {
364         private final Resources mResources;
365         private final AsyncSensorManager mSensorManager;
366         private final Execution mExecution;
367 
368         @Inject
BuilderFactory( @ain Resources resources, AsyncSensorManager sensorManager, Execution execution)369         BuilderFactory(
370                 @Main Resources resources,
371                 AsyncSensorManager sensorManager,
372                 Execution execution) {
373             mResources = resources;
374             mSensorManager = sensorManager;
375             mExecution = execution;
376         }
377 
createBuilder()378         ThresholdSensorImpl.Builder createBuilder() {
379             return new Builder(mResources, mSensorManager, mExecution);
380         }
381     }
382 }
383