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