DozeTriggers.java revision a957c56da0b255feaef7ec1a06e399bb6a814e61
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.app.AlarmManager; 20import android.app.UiModeManager; 21import android.content.BroadcastReceiver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.content.res.Configuration; 26import android.hardware.Sensor; 27import android.hardware.SensorEvent; 28import android.hardware.SensorEventListener; 29import android.hardware.SensorManager; 30import android.os.Handler; 31import android.os.SystemClock; 32import android.os.UserHandle; 33import android.text.format.Formatter; 34import android.util.Log; 35 36import com.android.internal.hardware.AmbientDisplayConfiguration; 37import com.android.internal.util.Preconditions; 38import com.android.systemui.statusbar.phone.DozeParameters; 39import com.android.systemui.util.Assert; 40import com.android.systemui.util.wakelock.WakeLock; 41 42import java.io.PrintWriter; 43import java.util.function.IntConsumer; 44 45/** 46 * Handles triggers for ambient state changes. 47 */ 48public class DozeTriggers implements DozeMachine.Part { 49 50 private static final String TAG = "DozeTriggers"; 51 private static final boolean DEBUG = DozeService.DEBUG; 52 53 /** adb shell am broadcast -a com.android.systemui.doze.pulse com.android.systemui */ 54 private static final String PULSE_ACTION = "com.android.systemui.doze.pulse"; 55 56 private final Context mContext; 57 private final DozeMachine mMachine; 58 private final DozeSensors mDozeSensors; 59 private final DozeHost mDozeHost; 60 private final AmbientDisplayConfiguration mConfig; 61 private final DozeParameters mDozeParameters; 62 private final SensorManager mSensorManager; 63 private final Handler mHandler; 64 private final WakeLock mWakeLock; 65 private final boolean mAllowPulseTriggers; 66 private final UiModeManager mUiModeManager; 67 private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver(); 68 69 private long mNotificationPulseTime; 70 private boolean mPulsePending; 71 72 73 public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost, 74 AlarmManager alarmManager, AmbientDisplayConfiguration config, 75 DozeParameters dozeParameters, SensorManager sensorManager, Handler handler, 76 WakeLock wakeLock, boolean allowPulseTriggers) { 77 mContext = context; 78 mMachine = machine; 79 mDozeHost = dozeHost; 80 mConfig = config; 81 mDozeParameters = dozeParameters; 82 mSensorManager = sensorManager; 83 mHandler = handler; 84 mWakeLock = wakeLock; 85 mAllowPulseTriggers = allowPulseTriggers; 86 mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters, 87 config, wakeLock, this::onSensor, this::onProximityFar); 88 mUiModeManager = mContext.getSystemService(UiModeManager.class); 89 } 90 91 private void onNotification() { 92 if (DozeMachine.DEBUG) Log.d(TAG, "requestNotificationPulse"); 93 mNotificationPulseTime = SystemClock.elapsedRealtime(); 94 if (!mConfig.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) return; 95 requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */); 96 DozeLog.traceNotificationPulse(mContext); 97 } 98 99 private void onWhisper() { 100 requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */); 101 } 102 103 private void proximityCheckThenCall(IntConsumer callback, 104 boolean alreadyPerformedProxCheck, 105 int pulseReason) { 106 Boolean cachedProxFar = mDozeSensors.isProximityCurrentlyFar(); 107 if (alreadyPerformedProxCheck) { 108 callback.accept(ProximityCheck.RESULT_NOT_CHECKED); 109 } else if (cachedProxFar != null) { 110 callback.accept(cachedProxFar ? ProximityCheck.RESULT_FAR : ProximityCheck.RESULT_NEAR); 111 } else { 112 final long start = SystemClock.uptimeMillis(); 113 new ProximityCheck() { 114 @Override 115 public void onProximityResult(int result) { 116 final long end = SystemClock.uptimeMillis(); 117 DozeLog.traceProximityResult(mContext, result == RESULT_NEAR, 118 end - start, pulseReason); 119 callback.accept(result); 120 } 121 }.check(); 122 } 123 } 124 125 private void onSensor(int pulseReason, boolean sensorPerformedProxCheck, 126 float screenX, float screenY) { 127 boolean isDoubleTap = pulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; 128 boolean isPickup = pulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP; 129 boolean isLongPress = pulseReason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS; 130 131 if (mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT) && !isLongPress) { 132 proximityCheckThenCall((result) -> { 133 if (result == ProximityCheck.RESULT_NEAR) { 134 // In pocket, drop event. 135 return; 136 } 137 if (isDoubleTap) { 138 mDozeHost.onDoubleTap(screenX, screenY); 139 mMachine.wakeUp(); 140 } else { 141 mDozeHost.extendPulse(); 142 } 143 }, sensorPerformedProxCheck, pulseReason); 144 return; 145 } else { 146 requestPulse(pulseReason, sensorPerformedProxCheck); 147 } 148 149 if (isPickup) { 150 final long timeSinceNotification = 151 SystemClock.elapsedRealtime() - mNotificationPulseTime; 152 final boolean withinVibrationThreshold = 153 timeSinceNotification < mDozeParameters.getPickupVibrationThreshold(); 154 DozeLog.tracePickupPulse(mContext, withinVibrationThreshold); 155 } 156 } 157 158 private void onProximityFar(boolean far) { 159 final boolean near = !far; 160 final DozeMachine.State state = mMachine.getState(); 161 final boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED); 162 final boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING); 163 final boolean aod = (state == DozeMachine.State.DOZE_AOD); 164 165 if (state == DozeMachine.State.DOZE_PULSING) { 166 boolean ignoreTouch = near; 167 if (DEBUG) Log.i(TAG, "Prox changed, ignore touch = " + ignoreTouch); 168 mDozeHost.onIgnoreTouchWhilePulsing(ignoreTouch); 169 } 170 if (far && (paused || pausing)) { 171 if (DEBUG) Log.i(TAG, "Prox FAR, unpausing AOD"); 172 mMachine.requestState(DozeMachine.State.DOZE_AOD); 173 } else if (near && aod) { 174 if (DEBUG) Log.i(TAG, "Prox NEAR, pausing AOD"); 175 mMachine.requestState(DozeMachine.State.DOZE_AOD_PAUSING); 176 } 177 } 178 179 @Override 180 public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) { 181 switch (newState) { 182 case INITIALIZED: 183 mBroadcastReceiver.register(mContext); 184 mDozeHost.addCallback(mHostCallback); 185 checkTriggersAtInit(); 186 break; 187 case DOZE: 188 case DOZE_AOD: 189 mDozeSensors.setProxListening(newState != DozeMachine.State.DOZE); 190 if (oldState != DozeMachine.State.INITIALIZED) { 191 mDozeSensors.reregisterAllSensors(); 192 } 193 mDozeSensors.setListening(true); 194 break; 195 case DOZE_AOD_PAUSED: 196 case DOZE_AOD_PAUSING: 197 mDozeSensors.setProxListening(true); 198 mDozeSensors.setListening(false); 199 break; 200 case DOZE_PULSING: 201 mDozeSensors.setTouchscreenSensorsListening(false); 202 mDozeSensors.setProxListening(true); 203 break; 204 case FINISH: 205 mBroadcastReceiver.unregister(mContext); 206 mDozeHost.removeCallback(mHostCallback); 207 mDozeSensors.setListening(false); 208 mDozeSensors.setProxListening(false); 209 break; 210 default: 211 } 212 } 213 214 private void checkTriggersAtInit() { 215 if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR 216 || mDozeHost.isPowerSaveActive() 217 || mDozeHost.isBlockingDoze() 218 || !mDozeHost.isProvisioned()) { 219 mMachine.requestState(DozeMachine.State.FINISH); 220 } 221 } 222 223 private void requestPulse(final int reason, boolean performedProxCheck) { 224 Assert.isMainThread(); 225 mDozeHost.extendPulse(); 226 if (mPulsePending || !mAllowPulseTriggers || !canPulse()) { 227 if (mAllowPulseTriggers) { 228 DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(), 229 mDozeHost.isPulsingBlocked()); 230 } 231 return; 232 } 233 234 mPulsePending = true; 235 proximityCheckThenCall((result) -> { 236 if (result == ProximityCheck.RESULT_NEAR) { 237 // in pocket, abort pulse 238 mPulsePending = false; 239 } else { 240 // not in pocket, continue pulsing 241 continuePulseRequest(reason); 242 } 243 }, !mDozeParameters.getProxCheckBeforePulse() || performedProxCheck, reason); 244 } 245 246 private boolean canPulse() { 247 return mMachine.getState() == DozeMachine.State.DOZE 248 || mMachine.getState() == DozeMachine.State.DOZE_AOD; 249 } 250 251 private void continuePulseRequest(int reason) { 252 mPulsePending = false; 253 if (mDozeHost.isPulsingBlocked() || !canPulse()) { 254 DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(), 255 mDozeHost.isPulsingBlocked()); 256 return; 257 } 258 mMachine.requestPulse(reason); 259 } 260 261 @Override 262 public void dump(PrintWriter pw) { 263 pw.print(" notificationPulseTime="); 264 pw.println(Formatter.formatShortElapsedTime(mContext, mNotificationPulseTime)); 265 266 pw.print(" pulsePending="); pw.println(mPulsePending); 267 pw.println("DozeSensors:"); 268 mDozeSensors.dump(pw); 269 } 270 271 private abstract class ProximityCheck implements SensorEventListener, Runnable { 272 private static final int TIMEOUT_DELAY_MS = 500; 273 274 protected static final int RESULT_UNKNOWN = 0; 275 protected static final int RESULT_NEAR = 1; 276 protected static final int RESULT_FAR = 2; 277 protected static final int RESULT_NOT_CHECKED = 3; 278 279 private boolean mRegistered; 280 private boolean mFinished; 281 private float mMaxRange; 282 283 protected abstract void onProximityResult(int result); 284 285 public void check() { 286 Preconditions.checkState(!mFinished && !mRegistered); 287 final Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); 288 if (sensor == null) { 289 if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found"); 290 finishWithResult(RESULT_UNKNOWN); 291 return; 292 } 293 mDozeSensors.setDisableSensorsInterferingWithProximity(true); 294 295 mMaxRange = sensor.getMaximumRange(); 296 mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, 0, 297 mHandler); 298 mHandler.postDelayed(this, TIMEOUT_DELAY_MS); 299 mWakeLock.acquire(); 300 mRegistered = true; 301 } 302 303 @Override 304 public void onSensorChanged(SensorEvent event) { 305 if (event.values.length == 0) { 306 if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: Event has no values!"); 307 finishWithResult(RESULT_UNKNOWN); 308 } else { 309 if (DozeMachine.DEBUG) { 310 Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange); 311 } 312 final boolean isNear = event.values[0] < mMaxRange; 313 finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR); 314 } 315 } 316 317 @Override 318 public void run() { 319 if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No event received before timeout"); 320 finishWithResult(RESULT_UNKNOWN); 321 } 322 323 private void finishWithResult(int result) { 324 if (mFinished) return; 325 boolean wasRegistered = mRegistered; 326 if (mRegistered) { 327 mHandler.removeCallbacks(this); 328 mSensorManager.unregisterListener(this); 329 mDozeSensors.setDisableSensorsInterferingWithProximity(false); 330 mRegistered = false; 331 } 332 onProximityResult(result); 333 if (wasRegistered) { 334 mWakeLock.release(); 335 } 336 mFinished = true; 337 } 338 339 @Override 340 public void onAccuracyChanged(Sensor sensor, int accuracy) { 341 // noop 342 } 343 } 344 345 private class TriggerReceiver extends BroadcastReceiver { 346 private boolean mRegistered; 347 348 @Override 349 public void onReceive(Context context, Intent intent) { 350 if (PULSE_ACTION.equals(intent.getAction())) { 351 if (DozeMachine.DEBUG) Log.d(TAG, "Received pulse intent"); 352 requestPulse(DozeLog.PULSE_REASON_INTENT, false /* performedProxCheck */); 353 } 354 if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(intent.getAction())) { 355 mMachine.requestState(DozeMachine.State.FINISH); 356 } 357 if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) { 358 mDozeSensors.onUserSwitched(); 359 } 360 } 361 362 public void register(Context context) { 363 if (mRegistered) { 364 return; 365 } 366 IntentFilter filter = new IntentFilter(PULSE_ACTION); 367 filter.addAction(UiModeManager.ACTION_ENTER_CAR_MODE); 368 filter.addAction(Intent.ACTION_USER_SWITCHED); 369 context.registerReceiver(this, filter); 370 mRegistered = true; 371 } 372 373 public void unregister(Context context) { 374 if (!mRegistered) { 375 return; 376 } 377 context.unregisterReceiver(this); 378 mRegistered = false; 379 } 380 } 381 382 private DozeHost.Callback mHostCallback = new DozeHost.Callback() { 383 @Override 384 public void onNotificationHeadsUp() { 385 onNotification(); 386 } 387 388 @Override 389 public void onPowerSaveChanged(boolean active) { 390 if (active) { 391 mMachine.requestState(DozeMachine.State.FINISH); 392 } 393 } 394 }; 395} 396