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