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 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 new AlwaysOnDisplayPolicy(context)); 89 mUiModeManager = mContext.getSystemService(UiModeManager.class); 90 } 91 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 100 private void onWhisper() { 101 requestPulse(DozeLog.PULSE_REASON_NOTIFICATION, false /* performedProxCheck */); 102 } 103 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 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 247 private boolean canPulse() { 248 return mMachine.getState() == DozeMachine.State.DOZE 249 || mMachine.getState() == DozeMachine.State.DOZE_AOD; 250 } 251 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 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 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 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