DozeSensors.java revision 8f72b3c74ac9c474f4d3223d38d7779168338214
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 } 178 179 private class ProxSensor implements SensorEventListener { 180 181 static final long COOLDOWN_TRIGGER = 2 * 1000; 182 static final long COOLDOWN_PERIOD = 5 * 1000; 183 184 boolean mRequested; 185 boolean mRegistered; 186 Boolean mCurrentlyFar; 187 long mLastNear; 188 final AlarmTimeout mCooldownTimer; 189 190 191 public ProxSensor() { 192 mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered, 193 "prox_cooldown", mHandler); 194 } 195 196 void setRequested(boolean requested) { 197 if (mRequested == requested) { 198 // Send an update even if we don't re-register. 199 mHandler.post(() -> { 200 if (mCurrentlyFar != null) { 201 mProxCallback.accept(mCurrentlyFar); 202 } 203 }); 204 return; 205 } 206 mRequested = requested; 207 updateRegistered(); 208 } 209 210 private void updateRegistered() { 211 setRegistered(mRequested && !mCooldownTimer.isScheduled()); 212 } 213 214 private void setRegistered(boolean register) { 215 if (mRegistered == register) { 216 return; 217 } 218 if (register) { 219 mRegistered = mSensorManager.registerListener(this, 220 mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY), 221 SensorManager.SENSOR_DELAY_NORMAL, mHandler); 222 } else { 223 mSensorManager.unregisterListener(this); 224 mRegistered = false; 225 mCurrentlyFar = null; 226 } 227 } 228 229 @Override 230 public void onSensorChanged(SensorEvent event) { 231 mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange(); 232 mProxCallback.accept(mCurrentlyFar); 233 234 long now = SystemClock.elapsedRealtime(); 235 if (mCurrentlyFar == null) { 236 // Sensor has been unregistered by the proxCallback. Do nothing. 237 } else if (!mCurrentlyFar) { 238 mLastNear = now; 239 } else if (mCurrentlyFar && now - mLastNear < COOLDOWN_TRIGGER) { 240 // If the last near was very recent, we might be using more power for prox 241 // wakeups than we're saving from turning of the screen. Instead, turn it off 242 // for a while. 243 mCooldownTimer.schedule(COOLDOWN_PERIOD, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED); 244 updateRegistered(); 245 } 246 } 247 248 @Override 249 public void onAccuracyChanged(Sensor sensor, int accuracy) { 250 } 251 } 252 253 private class TriggerSensor extends TriggerEventListener { 254 final Sensor mSensor; 255 final boolean mConfigured; 256 final int mPulseReason; 257 final String mSetting; 258 final boolean mReportsTouchCoordinates; 259 260 private boolean mRequested; 261 private boolean mRegistered; 262 private boolean mDisabled; 263 264 public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, 265 boolean reportsTouchCoordinates) { 266 mSensor = sensor; 267 mSetting = setting; 268 mConfigured = configured; 269 mPulseReason = pulseReason; 270 mReportsTouchCoordinates = reportsTouchCoordinates; 271 } 272 273 public void setListening(boolean listen) { 274 if (mRequested == listen) return; 275 mRequested = listen; 276 updateListener(); 277 } 278 279 public void setDisabled(boolean disabled) { 280 if (mDisabled == disabled) return; 281 mDisabled = disabled; 282 updateListener(); 283 } 284 285 public void updateListener() { 286 if (!mConfigured || mSensor == null) return; 287 if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { 288 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); 289 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); 290 } else if (mRegistered) { 291 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); 292 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt); 293 mRegistered = false; 294 } 295 } 296 297 private boolean enabledBySetting() { 298 if (TextUtils.isEmpty(mSetting)) { 299 return true; 300 } 301 return Settings.Secure.getIntForUser(mResolver, mSetting, 1, 302 UserHandle.USER_CURRENT) != 0; 303 } 304 305 @Override 306 public String toString() { 307 return new StringBuilder("{mRegistered=").append(mRegistered) 308 .append(", mRequested=").append(mRequested) 309 .append(", mDisabled=").append(mDisabled) 310 .append(", mConfigured=").append(mConfigured) 311 .append(", mSensor=").append(mSensor).append("}").toString(); 312 } 313 314 @Override 315 @AnyThread 316 public void onTrigger(TriggerEvent event) { 317 mHandler.post(mWakeLock.wrap(() -> { 318 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event)); 319 boolean sensorPerformsProxCheck = false; 320 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 321 int subType = (int) event.values[0]; 322 MetricsLogger.action( 323 mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE, 324 subType); 325 sensorPerformsProxCheck = 326 mDozeParameters.getPickupSubtypePerformsProxCheck(subType); 327 } 328 329 mRegistered = false; 330 float screenX = -1; 331 float screenY = -1; 332 if (mReportsTouchCoordinates && event.values.length >= 2) { 333 screenX = event.values[0]; 334 screenY = event.values[1]; 335 } 336 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY); 337 updateListener(); // reregister, this sensor only fires once 338 })); 339 } 340 341 public void registerSettingsObserver(ContentObserver settingsObserver) { 342 if (mConfigured && !TextUtils.isEmpty(mSetting)) { 343 mResolver.registerContentObserver( 344 Settings.Secure.getUriFor(mSetting), false /* descendants */, 345 mSettingsObserver, UserHandle.USER_ALL); 346 } 347 } 348 349 private String triggerEventToString(TriggerEvent event) { 350 if (event == null) return null; 351 final StringBuilder sb = new StringBuilder("TriggerEvent[") 352 .append(event.timestamp).append(',') 353 .append(event.sensor.getName()); 354 if (event.values != null) { 355 for (int i = 0; i < event.values.length; i++) { 356 sb.append(',').append(event.values[i]); 357 } 358 } 359 return sb.append(']').toString(); 360 } 361 } 362 363 public interface Callback { 364 365 /** 366 * Called when a sensor requests a pulse 367 * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP} 368 * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity. 369 * @param screenX the location on the screen where the sensor fired or -1 370 * if the sensor doesn't support reporting screen locations. 371 * @param screenY the location on the screen where the sensor fired or -1 372 * if the sensor doesn't support reporting screen locations. 373 */ 374 void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck, 375 float screenX, float screenY); 376 } 377} 378