1 /*
2  * Copyright (C) 2022 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.server.biometrics.log;
18 
19 import android.annotation.DurationMillisLong;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.hardware.Sensor;
23 import android.hardware.SensorEvent;
24 import android.hardware.SensorEventListener;
25 import android.hardware.SensorManager;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.util.Slog;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.server.biometrics.sensors.BaseClientMonitor;
32 
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.concurrent.TimeUnit;
36 import java.util.function.Consumer;
37 
38 /** Probe for ambient light. */
39 final class ALSProbe implements Probe {
40     private static final String TAG = "ALSProbe";
41 
42     @Nullable
43     private final SensorManager mSensorManager;
44     @Nullable
45     private final Sensor mLightSensor;
46     @NonNull
47     private final Handler mTimer;
48     @DurationMillisLong
49     private long mMaxSubscriptionTime = -1;
50 
51     private boolean mEnabled = false;
52     private boolean mDestroyed = false;
53     private boolean mDestroyRequested = false;
54     private boolean mDisableRequested = false;
55     private NextConsumer mNextConsumer = null;
56     private volatile float mLastAmbientLux = -1;
57 
58     private final SensorEventListener mLightSensorListener = new SensorEventListener() {
59         @Override
60         public void onSensorChanged(SensorEvent event) {
61             onNext(event.values[0]);
62         }
63 
64         @Override
65         public void onAccuracyChanged(Sensor sensor, int accuracy) {
66             // Not used.
67         }
68     };
69 
70     /**
71      * Create a probe with a 1-minute max sampling time.
72      *
73      * @param sensorManager Sensor manager
74      */
ALSProbe(@onNull SensorManager sensorManager)75     ALSProbe(@NonNull SensorManager sensorManager) {
76         this(sensorManager, new Handler(Looper.getMainLooper()),
77                 TimeUnit.MINUTES.toMillis(1));
78     }
79 
80     /**
81      * Create a probe with a given max sampling time.
82      *
83      * Note: The max time is a workaround for potential scheduler bugs where
84      * {@link BaseClientMonitor#destroy()} is not called due to an abnormal lifecycle. Clients
85      * should ensure that {@link #disable()} and {@link #destroy()} are called appropriately and
86      * avoid relying on this timeout to unsubscribe from the sensor when it is not needed.
87      *
88      * @param sensorManager Sensor manager
89      * @param handler Timeout handler
90      * @param maxTime The max amount of time to subscribe to events. If this time is exceeded
91      *                {@link #disable()} will be called and no sampling will occur until {@link
92      *                #enable()} is called again.
93      */
94     @VisibleForTesting
ALSProbe(@ullable SensorManager sensorManager, @NonNull Handler handler, @DurationMillisLong long maxTime)95     ALSProbe(@Nullable SensorManager sensorManager, @NonNull Handler handler,
96             @DurationMillisLong long maxTime) {
97         mSensorManager = sensorManager;
98         mLightSensor = sensorManager != null
99                 ? sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT) : null;
100         mTimer = handler;
101         mMaxSubscriptionTime = maxTime;
102 
103         if (mSensorManager == null || mLightSensor == null) {
104             Slog.w(TAG, "No sensor - probe disabled");
105             mDestroyed = true;
106         }
107     }
108 
109     @Override
enable()110     public synchronized void enable() {
111         if (!mDestroyed && !mDestroyRequested) {
112             mDisableRequested = false;
113             enableLightSensorLoggingLocked();
114         }
115     }
116 
117     @Override
disable()118     public synchronized void disable() {
119         mDisableRequested = true;
120 
121         // if a final consumer is set it will call destroy/disable on the next value if requested
122         if (!mDestroyed && mNextConsumer == null) {
123             disableLightSensorLoggingLocked(false /* destroying */);
124         }
125     }
126 
127     @Override
destroy()128     public synchronized void destroy() {
129         mDestroyRequested = true;
130 
131         // if a final consumer is set it will call destroy/disable on the next value if requested
132         if (!mDestroyed && mNextConsumer == null) {
133             disableLightSensorLoggingLocked(true /* destroying */);
134             mDestroyed = true;
135         }
136     }
137 
onNext(float value)138     private synchronized void onNext(float value) {
139         mLastAmbientLux = value;
140 
141         final NextConsumer consumer = mNextConsumer;
142         mNextConsumer = null;
143         if (consumer != null) {
144             Slog.v(TAG, "Finishing next consumer");
145 
146             if (mDestroyRequested) {
147                 destroy();
148             } else if (mDisableRequested) {
149                 disable();
150             }
151 
152             consumer.consume(value);
153         }
154     }
155 
156     /** The most recent lux reading. */
getMostRecentLux()157     public float getMostRecentLux() {
158         return mLastAmbientLux;
159     }
160 
161     /**
162      * Register a listener for the next available ALS reading, which will be reported to the given
163      * consumer even if this probe is {@link #disable()}'ed or {@link #destroy()}'ed before a value
164      * is available.
165      *
166      * This method is intended to be used for event logs that occur when the screen may be
167      * off and sampling may have been {@link #disable()}'ed. In these cases, this method will turn
168      * on the sensor (if needed), fetch & report the first value, and then destroy or disable this
169      * probe (if needed).
170      *
171      * @param consumer consumer to notify when the data is available
172      * @param handler handler for notifying the consumer, or null
173      */
awaitNextLux(@onNull Consumer<Float> consumer, @Nullable Handler handler)174     public synchronized void awaitNextLux(@NonNull Consumer<Float> consumer,
175             @Nullable Handler handler) {
176         final NextConsumer nextConsumer = new NextConsumer(consumer, handler);
177         final float current = mLastAmbientLux;
178         if (current > -1f) {
179             nextConsumer.consume(current);
180         } else if (mNextConsumer != null) {
181             mNextConsumer.add(nextConsumer);
182         } else {
183             mDestroyed = false;
184             mNextConsumer = nextConsumer;
185             enableLightSensorLoggingLocked();
186         }
187     }
188 
enableLightSensorLoggingLocked()189     private void enableLightSensorLoggingLocked() {
190         if (!mEnabled) {
191             mEnabled = true;
192             mLastAmbientLux = -1;
193             mSensorManager.registerListener(mLightSensorListener, mLightSensor,
194                     SensorManager.SENSOR_DELAY_NORMAL);
195             Slog.v(TAG, "Enable ALS: " + mLightSensorListener.hashCode());
196         }
197 
198         resetTimerLocked(true /* start */);
199     }
200 
disableLightSensorLoggingLocked(boolean destroying)201     private void disableLightSensorLoggingLocked(boolean destroying) {
202         resetTimerLocked(false /* start */);
203 
204         if (mEnabled) {
205             mEnabled = false;
206             if (!destroying) {
207                 mLastAmbientLux = -1;
208             }
209             mSensorManager.unregisterListener(mLightSensorListener);
210             Slog.v(TAG, "Disable ALS: " + mLightSensorListener.hashCode());
211         }
212     }
213 
resetTimerLocked(boolean start)214     private void resetTimerLocked(boolean start) {
215         mTimer.removeCallbacksAndMessages(this /* token */);
216         if (start && mMaxSubscriptionTime > 0) {
217             mTimer.postDelayed(this::onTimeout, this /* token */, mMaxSubscriptionTime);
218         }
219     }
220 
onTimeout()221     private synchronized void onTimeout() {
222         Slog.e(TAG, "Max time exceeded for ALS logger - disabling: "
223                 + mLightSensorListener.hashCode());
224 
225         // if consumers are waiting but there was no sensor change, complete them with the latest
226         // value before disabling
227         onNext(mLastAmbientLux);
228         disable();
229     }
230 
231     private static class NextConsumer {
232         @NonNull private final Consumer<Float> mConsumer;
233         @Nullable private final Handler mHandler;
234         @NonNull private final List<NextConsumer> mOthers = new ArrayList<>();
235 
NextConsumer(@onNull Consumer<Float> consumer, @Nullable Handler handler)236         private NextConsumer(@NonNull Consumer<Float> consumer, @Nullable Handler handler) {
237             mConsumer = consumer;
238             mHandler = handler;
239         }
240 
consume(float value)241         public void consume(float value) {
242             if (mHandler != null) {
243                 mHandler.post(() -> mConsumer.accept(value));
244             } else {
245                 mConsumer.accept(value);
246             }
247             for (NextConsumer c : mOthers) {
248                 c.consume(value);
249             }
250         }
251 
add(NextConsumer consumer)252         public void add(NextConsumer consumer) {
253             mOthers.add(consumer);
254         }
255     }
256 }
257