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