DozeSensors.java revision 2981eb0d59f3568bfe84ce905c82fc17d62d21c5
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.content.ContentResolver; 22import android.content.Context; 23import android.database.ContentObserver; 24import android.hardware.Sensor; 25import android.hardware.SensorEvent; 26import android.hardware.SensorEventListener; 27import android.hardware.SensorManager; 28import android.hardware.TriggerEvent; 29import android.hardware.TriggerEventListener; 30import android.net.Uri; 31import android.os.Handler; 32import android.os.UserHandle; 33import android.provider.Settings; 34import android.text.TextUtils; 35import android.util.Log; 36 37import com.android.internal.hardware.AmbientDisplayConfiguration; 38import com.android.internal.logging.MetricsLogger; 39import com.android.internal.logging.nano.MetricsProto; 40import com.android.systemui.statusbar.phone.DozeParameters; 41import com.android.systemui.util.wakelock.WakeLock; 42 43import java.io.PrintWriter; 44import java.util.List; 45import java.util.function.Consumer; 46 47public class DozeSensors { 48 49 private static final boolean DEBUG = DozeService.DEBUG; 50 51 private static final String TAG = "DozeSensors"; 52 53 private final Context mContext; 54 private final SensorManager mSensorManager; 55 private final TriggerSensor[] mSensors; 56 private final ContentResolver mResolver; 57 private final TriggerSensor mPickupSensor; 58 private final DozeParameters mDozeParameters; 59 private final AmbientDisplayConfiguration mConfig; 60 private final WakeLock mWakeLock; 61 private final Consumer<Boolean> mProxCallback; 62 private final Callback mCallback; 63 64 private final Handler mHandler = new Handler(); 65 private final ProxSensor mProxSensor; 66 67 68 public DozeSensors(Context context, SensorManager sensorManager, DozeParameters dozeParameters, 69 AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, 70 Consumer<Boolean> proxCallback) { 71 mContext = context; 72 mSensorManager = sensorManager; 73 mDozeParameters = dozeParameters; 74 mConfig = config; 75 mWakeLock = wakeLock; 76 mProxCallback = proxCallback; 77 mResolver = mContext.getContentResolver(); 78 79 mSensors = new TriggerSensor[] { 80 new TriggerSensor( 81 mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION), 82 null /* setting */, 83 dozeParameters.getPulseOnSigMotion(), 84 DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */), 85 mPickupSensor = new TriggerSensor( 86 mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE), 87 Settings.Secure.DOZE_PULSE_ON_PICK_UP, 88 config.pulseOnPickupAvailable(), 89 DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */), 90 new TriggerSensor( 91 findSensorWithType(config.doubleTapSensorType()), 92 Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP, 93 true /* configured */, 94 DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP, 95 dozeParameters.doubleTapReportsTouchCoordinates()) 96 }; 97 98 mProxSensor = new ProxSensor(); 99 mCallback = callback; 100 } 101 102 private Sensor findSensorWithType(String type) { 103 return findSensorWithType(mSensorManager, type); 104 } 105 106 static Sensor findSensorWithType(SensorManager sensorManager, String type) { 107 if (TextUtils.isEmpty(type)) { 108 return null; 109 } 110 List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL); 111 for (Sensor s : sensorList) { 112 if (type.equals(s.getStringType())) { 113 return s; 114 } 115 } 116 return null; 117 } 118 119 public void setListening(boolean listen) { 120 for (TriggerSensor s : mSensors) { 121 s.setListening(listen); 122 if (listen) { 123 s.registerSettingsObserver(mSettingsObserver); 124 } 125 } 126 if (!listen) { 127 mResolver.unregisterContentObserver(mSettingsObserver); 128 } 129 } 130 131 public void reregisterAllSensors() { 132 for (TriggerSensor s : mSensors) { 133 s.setListening(false); 134 } 135 for (TriggerSensor s : mSensors) { 136 s.setListening(true); 137 } 138 } 139 140 public void onUserSwitched() { 141 for (TriggerSensor s : mSensors) { 142 s.updateListener(); 143 } 144 } 145 146 public void setProxListening(boolean listen) { 147 mProxSensor.setRegistered(listen); 148 } 149 150 private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) { 151 @Override 152 public void onChange(boolean selfChange, Uri uri, int userId) { 153 if (userId != ActivityManager.getCurrentUser()) { 154 return; 155 } 156 for (TriggerSensor s : mSensors) { 157 s.updateListener(); 158 } 159 } 160 }; 161 162 public void setDisableSensorsInterferingWithProximity(boolean disable) { 163 mPickupSensor.setDisabled(disable); 164 } 165 166 /** Dump current state */ 167 public void dump(PrintWriter pw) { 168 for (TriggerSensor s : mSensors) { 169 pw.print("Sensor: "); pw.println(s.toString()); 170 } 171 } 172 173 private class ProxSensor implements SensorEventListener { 174 175 boolean mRegistered; 176 Boolean mCurrentlyFar; 177 178 void setRegistered(boolean register) { 179 if (mRegistered == register) { 180 // Send an update even if we don't re-register. 181 mHandler.post(() -> { 182 if (mCurrentlyFar != null) { 183 mProxCallback.accept(mCurrentlyFar); 184 } 185 }); 186 return; 187 } 188 if (register) { 189 mRegistered = mSensorManager.registerListener(this, 190 mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY), 191 SensorManager.SENSOR_DELAY_NORMAL, mHandler); 192 } else { 193 mSensorManager.unregisterListener(this); 194 mRegistered = false; 195 mCurrentlyFar = null; 196 } 197 } 198 199 @Override 200 public void onSensorChanged(SensorEvent event) { 201 mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange(); 202 mProxCallback.accept(mCurrentlyFar); 203 } 204 205 @Override 206 public void onAccuracyChanged(Sensor sensor, int accuracy) { 207 } 208 } 209 210 private class TriggerSensor extends TriggerEventListener { 211 final Sensor mSensor; 212 final boolean mConfigured; 213 final int mPulseReason; 214 final String mSetting; 215 final boolean mReportsTouchCoordinates; 216 217 private boolean mRequested; 218 private boolean mRegistered; 219 private boolean mDisabled; 220 221 public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason, 222 boolean reportsTouchCoordinates) { 223 mSensor = sensor; 224 mSetting = setting; 225 mConfigured = configured; 226 mPulseReason = pulseReason; 227 mReportsTouchCoordinates = reportsTouchCoordinates; 228 } 229 230 public void setListening(boolean listen) { 231 if (mRequested == listen) return; 232 mRequested = listen; 233 updateListener(); 234 } 235 236 public void setDisabled(boolean disabled) { 237 if (mDisabled == disabled) return; 238 mDisabled = disabled; 239 updateListener(); 240 } 241 242 public void updateListener() { 243 if (!mConfigured || mSensor == null) return; 244 if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) { 245 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor); 246 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered); 247 } else if (mRegistered) { 248 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor); 249 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt); 250 mRegistered = false; 251 } 252 } 253 254 private boolean enabledBySetting() { 255 if (TextUtils.isEmpty(mSetting)) { 256 return true; 257 } 258 return Settings.Secure.getIntForUser(mResolver, mSetting, 1, 259 UserHandle.USER_CURRENT) != 0; 260 } 261 262 @Override 263 public String toString() { 264 return new StringBuilder("{mRegistered=").append(mRegistered) 265 .append(", mRequested=").append(mRequested) 266 .append(", mDisabled=").append(mDisabled) 267 .append(", mConfigured=").append(mConfigured) 268 .append(", mSensor=").append(mSensor).append("}").toString(); 269 } 270 271 @Override 272 @AnyThread 273 public void onTrigger(TriggerEvent event) { 274 mHandler.post(mWakeLock.wrap(() -> { 275 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event)); 276 boolean sensorPerformsProxCheck = false; 277 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) { 278 int subType = (int) event.values[0]; 279 MetricsLogger.action( 280 mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE, 281 subType); 282 sensorPerformsProxCheck = 283 mDozeParameters.getPickupSubtypePerformsProxCheck(subType); 284 } 285 286 mRegistered = false; 287 float screenX = -1; 288 float screenY = -1; 289 if (mReportsTouchCoordinates && event.values.length >= 2) { 290 screenX = event.values[0]; 291 screenY = event.values[1]; 292 } 293 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY); 294 updateListener(); // reregister, this sensor only fires once 295 })); 296 } 297 298 public void registerSettingsObserver(ContentObserver settingsObserver) { 299 if (mConfigured && !TextUtils.isEmpty(mSetting)) { 300 mResolver.registerContentObserver( 301 Settings.Secure.getUriFor(mSetting), false /* descendants */, 302 mSettingsObserver, UserHandle.USER_ALL); 303 } 304 } 305 306 private String triggerEventToString(TriggerEvent event) { 307 if (event == null) return null; 308 final StringBuilder sb = new StringBuilder("TriggerEvent[") 309 .append(event.timestamp).append(',') 310 .append(event.sensor.getName()); 311 if (event.values != null) { 312 for (int i = 0; i < event.values.length; i++) { 313 sb.append(',').append(event.values[i]); 314 } 315 } 316 return sb.append(']').toString(); 317 } 318 } 319 320 public interface Callback { 321 322 /** 323 * Called when a sensor requests a pulse 324 * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP} 325 * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity. 326 * @param screenX the location on the screen where the sensor fired or -1 327 * if the sensor doesn't support reporting screen locations. 328 * @param screenY the location on the screen where the sensor fired or -1 329 * if the sensor doesn't support reporting screen locations. 330 */ 331 void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck, 332 float screenX, float screenY); 333 } 334} 335