Home | History | Annotate | Download | only in doze
      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