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