DozeSensors.java revision d7aa26f33b70071a089bfcd7ae17e18d066501f0
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) { 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 mPickupSensor = new TriggerSensor( 92 mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), 93 Settings.Secure.DOZE_PULSE_ON_PICK_UP, 94 config.pulseOnPickupAvailable(), 95 DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */), 96 new TriggerSensor( 97 findSensorWithType(config.doubleTapSensorType()), 98 Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, 99 true /* configured */, 100 DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP, 101 dozeParameters.doubleTapReportsTouchCoordinates()) 102 }; 103 104 mProxSensor = new ProxSensor(); 105 mCallback = callback; 106 } 107 108 private Sensor findSensorWithType(String type) { 109 return findSensorWithType(mSensorManager, type); 110 } 111 112 static Sensor findSensorWithType(SensorManager sensorManager, String type) { 113 if (TextUtils.isEmpty(type)) { 114 return null; 115 } 116 List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL); 117 for (Sensor s : sensorList) { 118 if (type.equals(s.getStringType())) { 119 return s; 120 } 121 } 122 return null; 123 } 124 125 public void setListening(boolean listen) { 126 for (TriggerSensor s : mSensors) { 127 s.setListening(listen); 128 if (listen) { 129 s.registerSettingsObserver(mSettingsObserver); 130 } 131 } 132 if (!listen) { 133 mResolver.unregisterContentObserver(mSettingsObserver); 134 } 135 } 136 137 public void reregisterAllSensors() { 138 for (TriggerSensor s : mSensors) { 139 s.setListening(false); 140 } 141 for (TriggerSensor s : mSensors) { 142 s.setListening(true); 143 } 144 } 145 146 public void onUserSwitched() { 147 for (TriggerSensor s : mSensors) { 148 s.updateListener(); 149 } 150 } 151 152 public void setProxListening(boolean listen) { 153 mProxSensor.setRequested(listen); 154 } 155 156 private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 157 @Override 158 public void onChange(boolean selfChange, Uri uri, int userId) { 159 if (userId != ActivityManager.getCurrentUser()) { 160 return; 161 } 162 for (TriggerSensor s : mSensors) { 163 s.updateListener(); 164 } 165 } 166 }; 167 168 public void setDisableSensorsInterferingWithProximity(boolean disable) { 169 mPickupSensor.setDisabled(disable); 170 } 171 172 /** Dump current state */ 173 public void dump(PrintWriter pw) { 174 for (TriggerSensor s : mSensors) { 175 pw.print("Sensor: "); pw.println(s.toString()); 176 } 177 pw.print("ProxSensor: "); pw.println(mProxSensor.toString()); 178 } 179 180 private class ProxSensor implements SensorEventListener { 181 182 static final long COOLDOWN_TRIGGER = 2 * 1000; 183 static final long COOLDOWN_PERIOD = 5 * 1000; 184 185 boolean mRequested; 186 boolean mRegistered; 187 Boolean mCurrentlyFar; 188 long mLastNear; 189 final AlarmTimeout mCooldownTimer; 190 191 192 public ProxSensor() { 193 mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered, 194 "prox_cooldown", mHandler); 195 } 196 197 void setRequested(boolean requested) { 198 if (mRequested == requested) { 199 // Send an update even if we don't re-register. 200 mHandler.post(() -> { 201 if (mCurrentlyFar != null) { 202 mProxCallback.accept(mCurrentlyFar); 203 } 204 }); 205 return; 206 } 207 mRequested = requested; 208 updateRegistered(); 209 } 210 211 private void updateRegistered() { 212 setRegistered(mRequested && !mCooldownTimer.isScheduled()); 213 } 214 215 private void setRegistered(boolean register) { 216 if (mRegistered == register) { 217 return; 218 } 219 if (register) { 220 mRegistered = mSensorManager.registerListener(this, 221 mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY), 222 SensorManager.SENSOR_DELAY_NORMAL, mHandler); 223 } else { 224 mSensorManager.unregisterListener(this); 225 mRegistered = false; 226 mCurrentlyFar = null; 227 } 228 } 229 230 @Override 231 public void onSensorChanged(SensorEvent event) { 232 mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange(); 233 mProxCallback.accept(mCurrentlyFar); 234 235 long now = SystemClock.elapsedRealtime(); 236 if (mCurrentlyFar == null) { 237 // Sensor has been unregistered by the proxCallback. Do nothing. 238 } else if (!mCurrentlyFar) { 239 mLastNear = now; 240 } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) { 241 // If the last near was very recent, we might be using more power for prox 242 // wakeups than we're saving from turning of the screen. Instead, turn it off 243 // for a while. 244 mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 245 updateRegistered(); 246 } 247 } 248 249 @Override 250 public void onAccuracyChanged(Sensor sensor, int accuracy) { 251 } 252 253 @Override 254 public String toString() { 255 return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}", 256 mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar); 257 } 258 } 259 260 private class TriggerSensor extends TriggerEventListener { 261 final Sensor mSensor; 262 final boolean mConfigured; 263 final int mPulseReason; 264 final String mSetting; 265 final boolean mReportsTouchCoordinates; 266 267 private boolean mRequested; 268 private boolean mRegistered; 269 private boolean mDisabled; 270 271 public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, 272 boolean reportsTouchCoordinates) { 273 mSensor = sensor; 274 mSetting = setting; 275 mConfigured = configured; 276 mPulseReason = pulseReason; 277 mReportsTouchCoordinates = reportsTouchCoordinates; 278 } 279 280 public void setListening(boolean listen) { 281 if (mRequested == listen) return; 282 mRequested = listen; 283 updateListener(); 284 } 285 286 public void setDisabled(boolean disabled) { 287 if (mDisabled == disabled) return; 288 mDisabled = disabled; 289 updateListener(); 290 } 291 292 public void updateListener() { 293 if (!mConfigured || mSensor == null) return; 294 if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { 295 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); 296 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); 297 } else if (mRegistered) { 298 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); 299 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt); 300 mRegistered = false; 301 } 302 } 303 304 private boolean enabledBySetting() { 305 if (TextUtils.isEmpty(mSetting)) { 306 return true; 307 } 308 return Settings.Secure.getIntForUser(mResolver, mSetting, 1, 309 UserHandle.USER_CURRENT) != 0; 310 } 311 312 @Override 313 public String toString() { 314 return new StringBuilder("{mRegistered=").append(mRegistered) 315 .append(", mRequested=").append(mRequested) 316 .append(", mDisabled=").append(mDisabled) 317 .append(", mConfigured=").append(mConfigured) 318 .append(", mSensor=").append(mSensor).append("}").toString(); 319 } 320 321 @Override 322 @AnyThread 323 public void onTrigger(TriggerEvent event) { 324 DozeLog.traceSensor(mContext, mPulseReason); 325 mHandler.post(mWakeLock.wrap(() -> { 326 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event)); 327 boolean sensorPerformsProxCheck = false; 328 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 329 int subType = (int) event.values[0]; 330 MetricsLogger.action( 331 mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE, 332 subType); 333 sensorPerformsProxCheck = 334 mDozeParameters.getPickupSubtypePerformsProxCheck(subType); 335 } 336 337 mRegistered = false; 338 float screenX = -1; 339 float screenY = -1; 340 if (mReportsTouchCoordinates && event.values.length >= 2) { 341 screenX = event.values[0]; 342 screenY = event.values[1]; 343 } 344 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY); 345 updateListener(); // reregister, this sensor only fires once 346 })); 347 } 348 349 public void registerSettingsObserver(ContentObserver settingsObserver) { 350 if (mConfigured && !TextUtils.isEmpty(mSetting)) { 351 mResolver.registerContentObserver( 352 Settings.Secure.getUriFor(mSetting), false /* descendants */, 353 mSettingsObserver, UserHandle.USER_ALL); 354 } 355 } 356 357 private String triggerEventToString(TriggerEvent event) { 358 if (event == null) return null; 359 final StringBuilder sb = new StringBuilder("TriggerEvent[") 360 .append(event.timestamp).append(',') 361 .append(event.sensor.getName()); 362 if (event.values != null) { 363 for (int i = 0; i < event.values.length; i++) { 364 sb.append(',').append(event.values[i]); 365 } 366 } 367 return sb.append(']').toString(); 368 } 369 } 370 371 public interface Callback { 372 373 /** 374 * Called when a sensor requests a pulse 375 * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP} 376 * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity. 377 * @param screenX the location on the screen where the sensor fired or -1 378 * if the sensor doesn't support reporting screen locations. 379 * @param screenY the location on the screen where the sensor fired or -1 380 * if the sensor doesn't support reporting screen locations. 381 */ 382 void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck, 383 float screenX, float screenY); 384 } 385} 386