1/* 2 * Copyright (C) 2016 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 17package com.android.systemui.doze; 18 19import android.annotation.AnyThread; 20import android.app.ActivityManager; 21import android.app.AlarmManager; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.database.ContentObserver; 25import android.hardware.Sensor; 26import android.hardware.SensorEvent; 27import android.hardware.SensorEventListener; 28import android.hardware.SensorManager; 29import android.hardware.TriggerEvent; 30import android.hardware.TriggerEventListener; 31import android.net.Uri; 32import android.os.Handler; 33import android.os.SystemClock; 34import android.os.UserHandle; 35import android.provider.Settings; 36import android.text.TextUtils; 37import android.util.Log; 38 39import com.android.internal.hardware.AmbientDisplayConfiguration; 40import com.android.internal.logging.MetricsLogger; 41import com.android.internal.logging.nano.MetricsProto; 42import com.android.systemui.statusbar.phone.DozeParameters; 43import com.android.systemui.util.AlarmTimeout; 44import com.android.systemui.util.wakelock.WakeLock; 45 46import java.io.PrintWriter; 47import java.util.List; 48import java.util.function.Consumer; 49 50public class DozeSensors { 51 52 private static final boolean DEBUG = DozeService.DEBUG; 53 54 private static final String TAG = "DozeSensors"; 55 56 private final Context mContext; 57 private final AlarmManager mAlarmManager; 58 private final SensorManager mSensorManager; 59 private final TriggerSensor[] mSensors; 60 private final ContentResolver mResolver; 61 private final TriggerSensor mPickupSensor; 62 private final DozeParameters mDozeParameters; 63 private final AmbientDisplayConfiguration mConfig; 64 private final WakeLock mWakeLock; 65 private final Consumer<Boolean> mProxCallback; 66 private final Callback mCallback; 67 68 private final Handler mHandler = new Handler(); 69 private final ProxSensor mProxSensor; 70 71 72 public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager, 73 DozeParameters dozeParameters, 74 AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, 75 Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) { 76 mContext = context; 77 mAlarmManager = alarmManager; 78 mSensorManager = sensorManager; 79 mDozeParameters = dozeParameters; 80 mConfig = config; 81 mWakeLock = wakeLock; 82 mProxCallback = proxCallback; 83 mResolver = mContext.getContentResolver(); 84 85 mSensors = new TriggerSensor[] { 86 new TriggerSensor( 87 mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION), 88 null /* setting */, 89 dozeParameters.getPulseOnSigMotion(), 90 DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */, 91 false /* touchscreen */), 92 mPickupSensor = new TriggerSensor( 93 mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), 94 Settings.Secure.DOZE_PULSE_ON_PICK_UP, 95 config.pulseOnPickupAvailable(), 96 DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */, 97 false /* touchscreen */), 98 new TriggerSensor( 99 findSensorWithType(config.doubleTapSensorType()), 100 Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, 101 true /* configured */, 102 DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP, 103 dozeParameters.doubleTapReportsTouchCoordinates(), 104 true /* touchscreen */), 105 new TriggerSensor( 106 findSensorWithType(config.longPressSensorType()), 107 Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, 108 false /* settingDef */, 109 true /* configured */, 110 DozeLog.PULSE_REASON_SENSOR_LONG_PRESS, 111 true /* reports touch coordinates */, 112 true /* touchscreen */), 113 }; 114 115 mProxSensor = new ProxSensor(policy); 116 mCallback = callback; 117 } 118 119 private Sensor findSensorWithType(String type) { 120 return findSensorWithType(mSensorManager, type); 121 } 122 123 static Sensor findSensorWithType(SensorManager sensorManager, String type) { 124 if (TextUtils.isEmpty(type)) { 125 return null; 126 } 127 List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL); 128 for (Sensor s : sensorList) { 129 if (type.equals(s.getStringType())) { 130 return s; 131 } 132 } 133 return null; 134 } 135 136 public void setListening(boolean listen) { 137 for (TriggerSensor s : mSensors) { 138 s.setListening(listen); 139 if (listen) { 140 s.registerSettingsObserver(mSettingsObserver); 141 } 142 } 143 if (!listen) { 144 mResolver.unregisterContentObserver(mSettingsObserver); 145 } 146 } 147 148 /** Set the listening state of only the sensors that require the touchscreen. */ 149 public void setTouchscreenSensorsListening(boolean listening) { 150 for (TriggerSensor sensor : mSensors) { 151 if (sensor.mRequiresTouchscreen) { 152 sensor.setListening(listening); 153 } 154 } 155 } 156 157 public void reregisterAllSensors() { 158 for (TriggerSensor s : mSensors) { 159 s.setListening(false); 160 } 161 for (TriggerSensor s : mSensors) { 162 s.setListening(true); 163 } 164 } 165 166 public void onUserSwitched() { 167 for (TriggerSensor s : mSensors) { 168 s.updateListener(); 169 } 170 } 171 172 public void setProxListening(boolean listen) { 173 mProxSensor.setRequested(listen); 174 } 175 176 private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 177 @Override 178 public void onChange(boolean selfChange, Uri uri, int userId) { 179 if (userId != ActivityManager.getCurrentUser()) { 180 return; 181 } 182 for (TriggerSensor s : mSensors) { 183 s.updateListener(); 184 } 185 } 186 }; 187 188 public void setDisableSensorsInterferingWithProximity(boolean disable) { 189 mPickupSensor.setDisabled(disable); 190 } 191 192 /** Dump current state */ 193 public void dump(PrintWriter pw) { 194 for (TriggerSensor s : mSensors) { 195 pw.print("Sensor: "); pw.println(s.toString()); 196 } 197 pw.print("ProxSensor: "); pw.println(mProxSensor.toString()); 198 } 199 200 /** 201 * @return true if prox is currently far, false if near or null if unknown. 202 */ 203 public Boolean isProximityCurrentlyFar() { 204 return mProxSensor.mCurrentlyFar; 205 } 206 207 private class ProxSensor implements SensorEventListener { 208 209 boolean mRequested; 210 boolean mRegistered; 211 Boolean mCurrentlyFar; 212 long mLastNear; 213 final AlarmTimeout mCooldownTimer; 214 final AlwaysOnDisplayPolicy mPolicy; 215 216 217 public ProxSensor(AlwaysOnDisplayPolicy policy) { 218 mPolicy = policy; 219 mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered, 220 "prox_cooldown", mHandler); 221 } 222 223 void setRequested(boolean requested) { 224 if (mRequested == requested) { 225 // Send an update even if we don't re-register. 226 mHandler.post(() -> { 227 if (mCurrentlyFar != null) { 228 mProxCallback.accept(mCurrentlyFar); 229 } 230 }); 231 return; 232 } 233 mRequested = requested; 234 updateRegistered(); 235 } 236 237 private void updateRegistered() { 238 setRegistered(mRequested && !mCooldownTimer.isScheduled()); 239 } 240 241 private void setRegistered(boolean register) { 242 if (mRegistered == register) { 243 return; 244 } 245 if (register) { 246 mRegistered = mSensorManager.registerListener(this, 247 mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY), 248 SensorManager.SENSOR_DELAY_NORMAL, mHandler); 249 } else { 250 mSensorManager.unregisterListener(this); 251 mRegistered = false; 252 mCurrentlyFar = null; 253 } 254 } 255 256 @Override 257 public void onSensorChanged(SensorEvent event) { 258 if (DEBUG) Log.d(TAG, "onSensorChanged " + event); 259 260 mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange(); 261 mProxCallback.accept(mCurrentlyFar); 262 263 long now = SystemClock.elapsedRealtime(); 264 if (mCurrentlyFar == null) { 265 // Sensor has been unregistered by the proxCallback. Do nothing. 266 } else if (!mCurrentlyFar) { 267 mLastNear = now; 268 } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) { 269 // If the last near was very recent, we might be using more power for prox 270 // wakeups than we're saving from turning of the screen. Instead, turn it off 271 // for a while. 272 mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs, 273 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 274 updateRegistered(); 275 } 276 } 277 278 @Override 279 public void onAccuracyChanged(Sensor sensor, int accuracy) { 280 } 281 282 @Override 283 public String toString() { 284 return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}", 285 mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar); 286 } 287 } 288 289 private class TriggerSensor extends TriggerEventListener { 290 final Sensor mSensor; 291 final boolean mConfigured; 292 final int mPulseReason; 293 final String mSetting; 294 final boolean mReportsTouchCoordinates; 295 final boolean mSettingDefault; 296 final boolean mRequiresTouchscreen; 297 298 private boolean mRequested; 299 private boolean mRegistered; 300 private boolean mDisabled; 301 302 public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, 303 boolean reportsTouchCoordinates, boolean requiresTouchscreen) { 304 this(sensor, setting, true /* settingDef */, configured, pulseReason, 305 reportsTouchCoordinates, requiresTouchscreen); 306 } 307 308 public TriggerSensor(Sensor sensor, String setting, boolean settingDef, 309 boolean configured, int pulseReason, boolean reportsTouchCoordinates, 310 boolean requiresTouchscreen) { 311 mSensor = sensor; 312 mSetting = setting; 313 mSettingDefault = settingDef; 314 mConfigured = configured; 315 mPulseReason = pulseReason; 316 mReportsTouchCoordinates = reportsTouchCoordinates; 317 mRequiresTouchscreen = requiresTouchscreen; 318 } 319 320 public void setListening(boolean listen) { 321 if (mRequested == listen) return; 322 mRequested = listen; 323 updateListener(); 324 } 325 326 public void setDisabled(boolean disabled) { 327 if (mDisabled == disabled) return; 328 mDisabled = disabled; 329 updateListener(); 330 } 331 332 public void updateListener() { 333 if (!mConfigured || mSensor == null) return; 334 if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { 335 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); 336 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); 337 } else if (mRegistered) { 338 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); 339 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt); 340 mRegistered = false; 341 } 342 } 343 344 private boolean enabledBySetting() { 345 if (TextUtils.isEmpty(mSetting)) { 346 return true; 347 } 348 return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0, 349 UserHandle.USER_CURRENT) != 0; 350 } 351 352 @Override 353 public String toString() { 354 return new StringBuilder("{mRegistered=").append(mRegistered) 355 .append(", mRequested=").append(mRequested) 356 .append(", mDisabled=").append(mDisabled) 357 .append(", mConfigured=").append(mConfigured) 358 .append(", mSensor=").append(mSensor).append("}").toString(); 359 } 360 361 @Override 362 @AnyThread 363 public void onTrigger(TriggerEvent event) { 364 DozeLog.traceSensor(mContext, mPulseReason); 365 mHandler.post(mWakeLock.wrap(() -> { 366 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event)); 367 boolean sensorPerformsProxCheck = false; 368 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 369 int subType = (int) event.values[0]; 370 MetricsLogger.action( 371 mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE, 372 subType); 373 sensorPerformsProxCheck = 374 mDozeParameters.getPickupSubtypePerformsProxCheck(subType); 375 } 376 377 mRegistered = false; 378 float screenX = -1; 379 float screenY = -1; 380 if (mReportsTouchCoordinates && event.values.length >= 2) { 381 screenX = event.values[0]; 382 screenY = event.values[1]; 383 } 384 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY); 385 updateListener(); // reregister, this sensor only fires once 386 })); 387 } 388 389 public void registerSettingsObserver(ContentObserver settingsObserver) { 390 if (mConfigured && !TextUtils.isEmpty(mSetting)) { 391 mResolver.registerContentObserver( 392 Settings.Secure.getUriFor(mSetting), false /* descendants */, 393 mSettingsObserver, UserHandle.USER_ALL); 394 } 395 } 396 397 private String triggerEventToString(TriggerEvent event) { 398 if (event == null) return null; 399 final StringBuilder sb = new StringBuilder("TriggerEvent[") 400 .append(event.timestamp).append(',') 401 .append(event.sensor.getName()); 402 if (event.values != null) { 403 for (int i = 0; i < event.values.length; i++) { 404 sb.append(',').append(event.values[i]); 405 } 406 } 407 return sb.append(']').toString(); 408 } 409 } 410 411 public interface Callback { 412 413 /** 414 * Called when a sensor requests a pulse 415 * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP} 416 * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity. 417 * @param screenX the location on the screen where the sensor fired or -1 418 * if the sensor doesn't support reporting screen locations. 419 * @param screenY the location on the screen where the sensor fired or -1 420 * if the sensor doesn't support reporting screen locations. 421 */ 422 void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck, 423 float screenX, float screenY); 424 } 425} 426