1 /*
2  * Copyright (C) 2019 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.util.Log;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.systemui.R;
28 import com.android.systemui.dagger.qualifiers.Main;
29 import com.android.systemui.util.concurrency.DelayableExecutor;
30 
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.concurrent.atomic.AtomicBoolean;
35 import java.util.function.Consumer;
36 
37 import javax.inject.Inject;
38 
39 /**
40  * Simple wrapper around SensorManager customized for the Proximity sensor.
41  */
42 public class ProximitySensor {
43     private static final String TAG = "ProxSensor";
44     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
45 
46     private final Sensor mSensor;
47     private final AsyncSensorManager mSensorManager;
48     private final float mThreshold;
49     private List<ProximitySensorListener> mListeners = new ArrayList<>();
50     private String mTag = null;
51     @VisibleForTesting ProximityEvent mLastEvent;
52     private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;
53     @VisibleForTesting protected boolean mPaused;
54     private boolean mRegistered;
55     private final AtomicBoolean mAlerting = new AtomicBoolean();
56 
57     private SensorEventListener mSensorEventListener = new SensorEventListener() {
58         @Override
59         public synchronized void onSensorChanged(SensorEvent event) {
60             onSensorEvent(event);
61         }
62 
63         @Override
64         public void onAccuracyChanged(Sensor sensor, int accuracy) {
65         }
66     };
67 
68     @Inject
ProximitySensor(@ain Resources resources, AsyncSensorManager sensorManager)69     public ProximitySensor(@Main Resources resources,
70             AsyncSensorManager sensorManager) {
71         mSensorManager = sensorManager;
72 
73         Sensor sensor = findCustomProxSensor(resources);
74         float threshold = 0;
75         if (sensor != null) {
76             try {
77                 threshold = getCustomProxThreshold(resources);
78             } catch (IllegalStateException e) {
79                 Log.e(TAG, "Can not load custom proximity sensor.", e);
80                 sensor = null;
81             }
82         }
83         if (sensor == null) {
84             sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
85             if (sensor != null) {
86                 threshold = sensor.getMaximumRange();
87             }
88         }
89 
90         mThreshold = threshold;
91 
92         mSensor = sensor;
93     }
94 
setTag(String tag)95     public void setTag(String tag) {
96         mTag = tag;
97     }
98 
setSensorDelay(int sensorDelay)99     public void setSensorDelay(int sensorDelay) {
100         mSensorDelay = sensorDelay;
101     }
102 
103     /**
104      * Unregister with the {@link SensorManager} without unsetting listeners on this object.
105      */
pause()106     public void pause() {
107         mPaused = true;
108         unregisterInternal();
109     }
110 
111     /**
112      * Register with the {@link SensorManager}. No-op if no listeners are registered on this object.
113      */
resume()114     public void resume() {
115         mPaused = false;
116         registerInternal();
117     }
118     /**
119      * Returns a brightness sensor that can be used for proximity purposes.
120      */
findCustomProxSensor(Resources resources)121     private Sensor findCustomProxSensor(Resources resources) {
122         String sensorType = resources.getString(R.string.proximity_sensor_type);
123         if (sensorType.isEmpty()) {
124             return null;
125         }
126 
127         List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
128         Sensor sensor = null;
129         for (Sensor s : sensorList) {
130             if (sensorType.equals(s.getStringType())) {
131                 sensor = s;
132                 break;
133             }
134         }
135 
136         return sensor;
137     }
138 
139     /**
140      * Returns a threshold value that can be used along with {@link #findCustomProxSensor}
141      */
getCustomProxThreshold(Resources resources)142     private float getCustomProxThreshold(Resources resources) {
143         try {
144             return resources.getFloat(R.dimen.proximity_sensor_threshold);
145         } catch (Resources.NotFoundException e) {
146             throw new IllegalStateException("R.dimen.proximity_sensor_threshold must be set.");
147         }
148     }
149 
150     /**
151      * Returns true if we are registered with the SensorManager.
152      */
isRegistered()153     public boolean isRegistered() {
154         return mRegistered;
155     }
156 
157     /**
158      * Returns {@code false} if a Proximity sensor is not available.
159      */
getSensorAvailable()160     public boolean getSensorAvailable() {
161         return mSensor != null;
162     }
163 
164     /**
165      * Add a listener.
166      *
167      * Registers itself with the {@link SensorManager} if this is the first listener
168      * added. If a cool down is currently running, the sensor will be registered when it is over.
169      */
register(ProximitySensorListener listener)170     public boolean register(ProximitySensorListener listener) {
171         if (!getSensorAvailable()) {
172             return false;
173         }
174 
175         if (mListeners.contains(listener)) {
176             Log.d(TAG, "ProxListener registered multiple times: " + listener);
177         } else {
178             mListeners.add(listener);
179         }
180         registerInternal();
181 
182         return true;
183     }
184 
registerInternal()185     protected void registerInternal() {
186         if (mRegistered || mPaused || mListeners.isEmpty()) {
187             return;
188         }
189         logDebug("Registering sensor listener");
190         mRegistered = true;
191         mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay);
192     }
193 
194     /**
195      * Remove a listener.
196      *
197      * If all listeners are removed from an instance of this class,
198      * it will unregister itself with the SensorManager.
199      */
unregister(ProximitySensorListener listener)200     public void unregister(ProximitySensorListener listener) {
201         mListeners.remove(listener);
202         if (mListeners.size() == 0) {
203             unregisterInternal();
204         }
205     }
206 
unregisterInternal()207     protected void unregisterInternal() {
208         if (!mRegistered) {
209             return;
210         }
211         logDebug("unregistering sensor listener");
212         mSensorManager.unregisterListener(mSensorEventListener);
213         mRegistered = false;
214     }
215 
isNear()216     public Boolean isNear() {
217         return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null;
218     }
219 
220     /** Update all listeners with the last value this class received from the sensor. */
alertListeners()221     public void alertListeners() {
222         if (mAlerting.getAndSet(true)) {
223             return;
224         }
225 
226         List<ProximitySensorListener> listeners = new ArrayList<>(mListeners);
227         listeners.forEach(proximitySensorListener ->
228                 proximitySensorListener.onSensorEvent(mLastEvent));
229         mAlerting.set(false);
230     }
231 
onSensorEvent(SensorEvent event)232     private void onSensorEvent(SensorEvent event) {
233         boolean near = event.values[0] < mThreshold;
234         mLastEvent = new ProximityEvent(near, event.timestamp);
235         alertListeners();
236     }
237 
238     @Override
239     public String toString() {
240         return String.format("{registered=%s, paused=%s, near=%s, sensor=%s}",
241                 isRegistered(), mPaused, isNear(), mSensor);
242     }
243 
244     /**
245      * Convenience class allowing for briefly checking the proximity sensor.
246      */
247     public static class ProximityCheck implements Runnable {
248 
249         private final ProximitySensor mSensor;
250         private final DelayableExecutor mDelayableExecutor;
251         private List<Consumer<Boolean>> mCallbacks = new ArrayList<>();
252         private final ProximitySensor.ProximitySensorListener mListener;
253         private final AtomicBoolean mRegistered = new AtomicBoolean();
254 
255         @Inject
256         public ProximityCheck(ProximitySensor sensor, @Main DelayableExecutor delayableExecutor) {
257             mSensor = sensor;
258             mSensor.setTag("prox_check");
259             mDelayableExecutor = delayableExecutor;
260             mListener = this::onProximityEvent;
261         }
262 
263         /** Set a descriptive tag for the sensors registration. */
264         public void setTag(String tag) {
265             mSensor.setTag(tag);
266         }
267 
268         @Override
269         public void run() {
270             unregister();
271             mSensor.alertListeners();
272         }
273 
274         /**
275          * Query the proximity sensor, timing out if no result.
276          */
277         public void check(long timeoutMs, Consumer<Boolean> callback) {
278             if (!mSensor.getSensorAvailable()) {
279                 callback.accept(null);
280             }
281             mCallbacks.add(callback);
282             if (!mRegistered.getAndSet(true)) {
283                 mSensor.register(mListener);
284                 mDelayableExecutor.executeDelayed(this, timeoutMs);
285             }
286         }
287 
288         private void unregister() {
289             mSensor.unregister(mListener);
290             mRegistered.set(false);
291         }
292 
293         private void onProximityEvent(ProximityEvent proximityEvent) {
294             mCallbacks.forEach(
295                     booleanConsumer ->
296                             booleanConsumer.accept(
297                                     proximityEvent == null ? null : proximityEvent.getNear()));
298             mCallbacks.clear();
299             unregister();
300             mRegistered.set(false);
301         }
302     }
303 
304     /** Implement to be notified of ProximityEvents. */
305     public interface ProximitySensorListener {
306         /** Called when the ProximitySensor changes. */
onSensorEvent(ProximityEvent proximityEvent)307         void onSensorEvent(ProximityEvent proximityEvent);
308     }
309 
310     /**
311      * Returned when the near/far state of a {@link ProximitySensor} changes.
312      */
313     public static class ProximityEvent {
314         private final boolean mNear;
315         private final long mTimestampNs;
316 
ProximityEvent(boolean near, long timestampNs)317         public ProximityEvent(boolean near, long timestampNs) {
318             mNear = near;
319             mTimestampNs = timestampNs;
320         }
321 
getNear()322         public boolean getNear() {
323             return mNear;
324         }
325 
getTimestampNs()326         public long getTimestampNs() {
327             return mTimestampNs;
328         }
329 
getTimestampMs()330         public long getTimestampMs() {
331             return mTimestampNs / 1000000;
332         }
333 
334         @Override
toString()335         public String toString() {
336             return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs);
337         }
338 
339     }
340 
logDebug(String msg)341     private void logDebug(String msg) {
342         if (DEBUG) {
343             Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg);
344         }
345     }
346 }
347