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 17 package com.android.systemui.doze; 18 19 import android.annotation.AnyThread; 20 import android.app.ActivityManager; 21 import android.app.AlarmManager; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.database.ContentObserver; 25 import android.hardware.Sensor; 26 import android.hardware.SensorEvent; 27 import android.hardware.SensorEventListener; 28 import android.hardware.SensorManager; 29 import android.hardware.TriggerEvent; 30 import android.hardware.TriggerEventListener; 31 import android.net.Uri; 32 import android.os.Handler; 33 import android.os.SystemClock; 34 import android.os.UserHandle; 35 import android.provider.Settings; 36 import android.text.TextUtils; 37 import android.util.Log; 38 39 import com.android.internal.hardware.AmbientDisplayConfiguration; 40 import com.android.internal.logging.MetricsLogger; 41 import com.android.internal.logging.nano.MetricsProto; 42 import com.android.systemui.statusbar.phone.DozeParameters; 43 import com.android.systemui.util.AlarmTimeout; 44 import com.android.systemui.util.wakelock.WakeLock; 45 46 import java.io.PrintWriter; 47 import java.util.List; 48 import java.util.function.Consumer; 49 50 public 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 mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange(); 259 mProxCallback.accept(mCurrentlyFar); 260 261 long now = SystemClock.elapsedRealtime(); 262 if (mCurrentlyFar == null) { 263 // Sensor has been unregistered by the proxCallback. Do nothing. 264 } else if (!mCurrentlyFar) { 265 mLastNear = now; 266 } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) { 267 // If the last near was very recent, we might be using more power for prox 268 // wakeups than we're saving from turning of the screen. Instead, turn it off 269 // for a while. 270 mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs, 271 AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 272 updateRegistered(); 273 } 274 } 275 276 @Override 277 public void onAccuracyChanged(Sensor sensor, int accuracy) { 278 } 279 280 @Override 281 public String toString() { 282 return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}", 283 mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar); 284 } 285 } 286 287 private class TriggerSensor extends TriggerEventListener { 288 final Sensor mSensor; 289 final boolean mConfigured; 290 final int mPulseReason; 291 final String mSetting; 292 final boolean mReportsTouchCoordinates; 293 final boolean mSettingDefault; 294 final boolean mRequiresTouchscreen; 295 296 private boolean mRequested; 297 private boolean mRegistered; 298 private boolean mDisabled; 299 300 public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, 301 boolean reportsTouchCoordinates, boolean requiresTouchscreen) { 302 this(sensor, setting, true /* settingDef */, configured, pulseReason, 303 reportsTouchCoordinates, requiresTouchscreen); 304 } 305 306 public TriggerSensor(Sensor sensor, String setting, boolean settingDef, 307 boolean configured, int pulseReason, boolean reportsTouchCoordinates, 308 boolean requiresTouchscreen) { 309 mSensor = sensor; 310 mSetting = setting; 311 mSettingDefault = settingDef; 312 mConfigured = configured; 313 mPulseReason = pulseReason; 314 mReportsTouchCoordinates = reportsTouchCoordinates; 315 mRequiresTouchscreen = requiresTouchscreen; 316 } 317 318 public void setListening(boolean listen) { 319 if (mRequested == listen) return; 320 mRequested = listen; 321 updateListener(); 322 } 323 324 public void setDisabled(boolean disabled) { 325 if (mDisabled == disabled) return; 326 mDisabled = disabled; 327 updateListener(); 328 } 329 330 public void updateListener() { 331 if (!mConfigured || mSensor == null) return; 332 if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { 333 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); 334 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); 335 } else if (mRegistered) { 336 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); 337 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt); 338 mRegistered = false; 339 } 340 } 341 342 private boolean enabledBySetting() { 343 if (TextUtils.isEmpty(mSetting)) { 344 return true; 345 } 346 return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0, 347 UserHandle.USER_CURRENT) != 0; 348 } 349 350 @Override 351 public String toString() { 352 return new StringBuilder("{mRegistered=").append(mRegistered) 353 .append(", mRequested=").append(mRequested) 354 .append(", mDisabled=").append(mDisabled) 355 .append(", mConfigured=").append(mConfigured) 356 .append(", mSensor=").append(mSensor).append("}").toString(); 357 } 358 359 @Override 360 @AnyThread 361 public void onTrigger(TriggerEvent event) { 362 DozeLog.traceSensor(mContext, mPulseReason); 363 mHandler.post(mWakeLock.wrap(() -> { 364 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event)); 365 boolean sensorPerformsProxCheck = false; 366 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 367 int subType = (int) event.values[0]; 368 MetricsLogger.action( 369 mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE, 370 subType); 371 sensorPerformsProxCheck = 372 mDozeParameters.getPickupSubtypePerformsProxCheck(subType); 373 } 374 375 mRegistered = false; 376 float screenX = -1; 377 float screenY = -1; 378 if (mReportsTouchCoordinates && event.values.length >= 2) { 379 screenX = event.values[0]; 380 screenY = event.values[1]; 381 } 382 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY); 383 updateListener(); // reregister, this sensor only fires once 384 })); 385 } 386 387 public void registerSettingsObserver(ContentObserver settingsObserver) { 388 if (mConfigured && !TextUtils.isEmpty(mSetting)) { 389 mResolver.registerContentObserver( 390 Settings.Secure.getUriFor(mSetting), false /* descendants */, 391 mSettingsObserver, UserHandle.USER_ALL); 392 } 393 } 394 395 private String triggerEventToString(TriggerEvent event) { 396 if (event == null) return null; 397 final StringBuilder sb = new StringBuilder("TriggerEvent[") 398 .append(event.timestamp).append(',') 399 .append(event.sensor.getName()); 400 if (event.values != null) { 401 for (int i = 0; i < event.values.length; i++) { 402 sb.append(',').append(event.values[i]); 403 } 404 } 405 return sb.append(']').toString(); 406 } 407 } 408 409 public interface Callback { 410 411 /** 412 * Called when a sensor requests a pulse 413 * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP} 414 * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity. 415 * @param screenX the location on the screen where the sensor fired or -1 416 * if the sensor doesn't support reporting screen locations. 417 * @param screenY the location on the screen where the sensor fired or -1 418 * if the sensor doesn't support reporting screen locations. 419 */ 420 void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck, 421 float screenX, float screenY); 422 } 423 } 424