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.annotation.AnyThread;
     20 import android.app.ActivityManager;
     21 import android.app.AlarmManager;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.database.ContentObserver;
     25 import android.hardware.Sensor;
     26 import android.hardware.SensorEvent;
     27 import android.hardware.SensorEventListener;
     28 import android.hardware.SensorManager;
     29 import android.hardware.TriggerEvent;
     30 import android.hardware.TriggerEventListener;
     31 import android.net.Uri;
     32 import android.os.Handler;
     33 import android.os.SystemClock;
     34 import android.os.UserHandle;
     35 import android.provider.Settings;
     36 import android.text.TextUtils;
     37 import android.util.Log;
     38 
     39 import com.android.internal.hardware.AmbientDisplayConfiguration;
     40 import com.android.internal.logging.MetricsLogger;
     41 import com.android.internal.logging.nano.MetricsProto;
     42 import com.android.systemui.statusbar.phone.DozeParameters;
     43 import com.android.systemui.util.AlarmTimeout;
     44 import com.android.systemui.util.wakelock.WakeLock;
     45 
     46 import java.io.PrintWriter;
     47 import java.util.List;
     48 import java.util.function.Consumer;
     49 
     50 public class DozeSensors {
     51 
     52     private static final boolean DEBUG = DozeService.DEBUG;
     53 
     54     private static final String TAG = "DozeSensors";
     55 
     56     private final Context mContext;
     57     private final AlarmManager mAlarmManager;
     58     private final SensorManager mSensorManager;
     59     private final TriggerSensor[] mSensors;
     60     private final ContentResolver mResolver;
     61     private final TriggerSensor mPickupSensor;
     62     private final DozeParameters mDozeParameters;
     63     private final AmbientDisplayConfiguration mConfig;
     64     private final WakeLock mWakeLock;
     65     private final Consumer<Boolean> mProxCallback;
     66     private final Callback mCallback;
     67 
     68     private final Handler mHandler = new Handler();
     69     private final ProxSensor mProxSensor;
     70 
     71 
     72     public DozeSensors(Context context, AlarmManager alarmManager, SensorManager sensorManager,
     73             DozeParameters dozeParameters,
     74             AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback,
     75             Consumer<Boolean> proxCallback, AlwaysOnDisplayPolicy policy) {
     76         mContext = context;
     77         mAlarmManager = alarmManager;
     78         mSensorManager = sensorManager;
     79         mDozeParameters = dozeParameters;
     80         mConfig = config;
     81         mWakeLock = wakeLock;
     82         mProxCallback = proxCallback;
     83         mResolver = mContext.getContentResolver();
     84 
     85         mSensors = new TriggerSensor[] {
     86                 new TriggerSensor(
     87                         mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION),
     88                         null /* setting */,
     89                         dozeParameters.getPulseOnSigMotion(),
     90                         DozeLog.PULSE_REASON_SENSOR_SIGMOTION, false /* touchCoords */,
     91                         false /* touchscreen */),
     92                 mPickupSensor = new TriggerSensor(
     93                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
     94                         Settings.Secure.DOZE_PULSE_ON_PICK_UP,
     95                         config.pulseOnPickupAvailable(),
     96                         DozeLog.PULSE_REASON_SENSOR_PICKUP, false /* touchCoords */,
     97                         false /* touchscreen */),
     98                 new TriggerSensor(
     99                         findSensorWithType(config.doubleTapSensorType()),
    100                         Settings.Secure.DOZE_PULSE_ON_DOUBLE_TAP,
    101                         true /* configured */,
    102                         DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP,
    103                         dozeParameters.doubleTapReportsTouchCoordinates(),
    104                         true /* touchscreen */),
    105                 new TriggerSensor(
    106                         findSensorWithType(config.longPressSensorType()),
    107                         Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
    108                         false /* settingDef */,
    109                         true /* configured */,
    110                         DozeLog.PULSE_REASON_SENSOR_LONG_PRESS,
    111                         true /* reports touch coordinates */,
    112                         true /* touchscreen */),
    113         };
    114 
    115         mProxSensor = new ProxSensor(policy);
    116         mCallback = callback;
    117     }
    118 
    119     private Sensor findSensorWithType(String type) {
    120         return findSensorWithType(mSensorManager, type);
    121     }
    122 
    123     static Sensor findSensorWithType(SensorManager sensorManager, String type) {
    124         if (TextUtils.isEmpty(type)) {
    125             return null;
    126         }
    127         List<Sensor> sensorList = sensorManager.getSensorList(Sensor.TYPE_ALL);
    128         for (Sensor s : sensorList) {
    129             if (type.equals(s.getStringType())) {
    130                 return s;
    131             }
    132         }
    133         return null;
    134     }
    135 
    136     public void setListening(boolean listen) {
    137         for (TriggerSensor s : mSensors) {
    138             s.setListening(listen);
    139             if (listen) {
    140                 s.registerSettingsObserver(mSettingsObserver);
    141             }
    142         }
    143         if (!listen) {
    144             mResolver.unregisterContentObserver(mSettingsObserver);
    145         }
    146     }
    147 
    148     /** Set the listening state of only the sensors that require the touchscreen. */
    149     public void setTouchscreenSensorsListening(boolean listening) {
    150         for (TriggerSensor sensor : mSensors) {
    151             if (sensor.mRequiresTouchscreen) {
    152                 sensor.setListening(listening);
    153             }
    154         }
    155     }
    156 
    157     public void reregisterAllSensors() {
    158         for (TriggerSensor s : mSensors) {
    159             s.setListening(false);
    160         }
    161         for (TriggerSensor s : mSensors) {
    162             s.setListening(true);
    163         }
    164     }
    165 
    166     public void onUserSwitched() {
    167         for (TriggerSensor s : mSensors) {
    168             s.updateListener();
    169         }
    170     }
    171 
    172     public void setProxListening(boolean listen) {
    173         mProxSensor.setRequested(listen);
    174     }
    175 
    176     private final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
    177         @Override
    178         public void onChange(boolean selfChange, Uri uri, int userId) {
    179             if (userId != ActivityManager.getCurrentUser()) {
    180                 return;
    181             }
    182             for (TriggerSensor s : mSensors) {
    183                 s.updateListener();
    184             }
    185         }
    186     };
    187 
    188     public void setDisableSensorsInterferingWithProximity(boolean disable) {
    189         mPickupSensor.setDisabled(disable);
    190     }
    191 
    192     /** Dump current state */
    193     public void dump(PrintWriter pw) {
    194         for (TriggerSensor s : mSensors) {
    195             pw.print("Sensor: "); pw.println(s.toString());
    196         }
    197         pw.print("ProxSensor: "); pw.println(mProxSensor.toString());
    198     }
    199 
    200     /**
    201      * @return true if prox is currently far, false if near or null if unknown.
    202      */
    203     public Boolean isProximityCurrentlyFar() {
    204         return mProxSensor.mCurrentlyFar;
    205     }
    206 
    207     private class ProxSensor implements SensorEventListener {
    208 
    209         boolean mRequested;
    210         boolean mRegistered;
    211         Boolean mCurrentlyFar;
    212         long mLastNear;
    213         final AlarmTimeout mCooldownTimer;
    214         final AlwaysOnDisplayPolicy mPolicy;
    215 
    216 
    217         public ProxSensor(AlwaysOnDisplayPolicy policy) {
    218             mPolicy = policy;
    219             mCooldownTimer = new AlarmTimeout(mAlarmManager, this::updateRegistered,
    220                     "prox_cooldown", mHandler);
    221         }
    222 
    223         void setRequested(boolean requested) {
    224             if (mRequested == requested) {
    225                 // Send an update even if we don't re-register.
    226                 mHandler.post(() -> {
    227                     if (mCurrentlyFar != null) {
    228                         mProxCallback.accept(mCurrentlyFar);
    229                     }
    230                 });
    231                 return;
    232             }
    233             mRequested = requested;
    234             updateRegistered();
    235         }
    236 
    237         private void updateRegistered() {
    238             setRegistered(mRequested && !mCooldownTimer.isScheduled());
    239         }
    240 
    241         private void setRegistered(boolean register) {
    242             if (mRegistered == register) {
    243                 return;
    244             }
    245             if (register) {
    246                 mRegistered = mSensorManager.registerListener(this,
    247                         mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY),
    248                         SensorManager.SENSOR_DELAY_NORMAL, mHandler);
    249             } else {
    250                 mSensorManager.unregisterListener(this);
    251                 mRegistered = false;
    252                 mCurrentlyFar = null;
    253             }
    254         }
    255 
    256         @Override
    257         public void onSensorChanged(SensorEvent event) {
    258             if (DEBUG) Log.d(TAG, "onSensorChanged " + event);
    259 
    260             mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
    261             mProxCallback.accept(mCurrentlyFar);
    262 
    263             long now = SystemClock.elapsedRealtime();
    264             if (mCurrentlyFar == null) {
    265                 // Sensor has been unregistered by the proxCallback. Do nothing.
    266             } else if (!mCurrentlyFar) {
    267                 mLastNear = now;
    268             } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
    269                 // If the last near was very recent, we might be using more power for prox
    270                 // wakeups than we're saving from turning of the screen. Instead, turn it off
    271                 // for a while.
    272                 mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
    273                         AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
    274                 updateRegistered();
    275             }
    276         }
    277 
    278         @Override
    279         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    280         }
    281 
    282         @Override
    283         public String toString() {
    284             return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}",
    285                     mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar);
    286         }
    287     }
    288 
    289     private class TriggerSensor extends TriggerEventListener {
    290         final Sensor mSensor;
    291         final boolean mConfigured;
    292         final int mPulseReason;
    293         final String mSetting;
    294         final boolean mReportsTouchCoordinates;
    295         final boolean mSettingDefault;
    296         final boolean mRequiresTouchscreen;
    297 
    298         private boolean mRequested;
    299         private boolean mRegistered;
    300         private boolean mDisabled;
    301 
    302         public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
    303                 boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
    304             this(sensor, setting, true /* settingDef */, configured, pulseReason,
    305                     reportsTouchCoordinates, requiresTouchscreen);
    306         }
    307 
    308         public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
    309                 boolean configured, int pulseReason, boolean reportsTouchCoordinates,
    310                 boolean requiresTouchscreen) {
    311             mSensor = sensor;
    312             mSetting = setting;
    313             mSettingDefault = settingDef;
    314             mConfigured = configured;
    315             mPulseReason = pulseReason;
    316             mReportsTouchCoordinates = reportsTouchCoordinates;
    317             mRequiresTouchscreen = requiresTouchscreen;
    318         }
    319 
    320         public void setListening(boolean listen) {
    321             if (mRequested == listen) return;
    322             mRequested = listen;
    323             updateListener();
    324         }
    325 
    326         public void setDisabled(boolean disabled) {
    327             if (mDisabled == disabled) return;
    328             mDisabled = disabled;
    329             updateListener();
    330         }
    331 
    332         public void updateListener() {
    333             if (!mConfigured || mSensor == null) return;
    334             if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
    335                 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
    336                 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
    337             } else if (mRegistered) {
    338                 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
    339                 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
    340                 mRegistered = false;
    341             }
    342         }
    343 
    344         private boolean enabledBySetting() {
    345             if (TextUtils.isEmpty(mSetting)) {
    346                 return true;
    347             }
    348             return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0,
    349                     UserHandle.USER_CURRENT) != 0;
    350         }
    351 
    352         @Override
    353         public String toString() {
    354             return new StringBuilder("{mRegistered=").append(mRegistered)
    355                     .append(", mRequested=").append(mRequested)
    356                     .append(", mDisabled=").append(mDisabled)
    357                     .append(", mConfigured=").append(mConfigured)
    358                     .append(", mSensor=").append(mSensor).append("}").toString();
    359         }
    360 
    361         @Override
    362         @AnyThread
    363         public void onTrigger(TriggerEvent event) {
    364             DozeLog.traceSensor(mContext, mPulseReason);
    365             mHandler.post(mWakeLock.wrap(() -> {
    366                 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
    367                 boolean sensorPerformsProxCheck = false;
    368                 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
    369                     int subType = (int) event.values[0];
    370                     MetricsLogger.action(
    371                             mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE,
    372                             subType);
    373                     sensorPerformsProxCheck =
    374                             mDozeParameters.getPickupSubtypePerformsProxCheck(subType);
    375                 }
    376 
    377                 mRegistered = false;
    378                 float screenX = -1;
    379                 float screenY = -1;
    380                 if (mReportsTouchCoordinates && event.values.length >= 2) {
    381                     screenX = event.values[0];
    382                     screenY = event.values[1];
    383                 }
    384                 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY);
    385                 updateListener();  // reregister, this sensor only fires once
    386             }));
    387         }
    388 
    389         public void registerSettingsObserver(ContentObserver settingsObserver) {
    390             if (mConfigured && !TextUtils.isEmpty(mSetting)) {
    391                 mResolver.registerContentObserver(
    392                         Settings.Secure.getUriFor(mSetting), false /* descendants */,
    393                         mSettingsObserver, UserHandle.USER_ALL);
    394             }
    395         }
    396 
    397         private String triggerEventToString(TriggerEvent event) {
    398             if (event == null) return null;
    399             final StringBuilder sb = new StringBuilder("TriggerEvent[")
    400                     .append(event.timestamp).append(',')
    401                     .append(event.sensor.getName());
    402             if (event.values != null) {
    403                 for (int i = 0; i < event.values.length; i++) {
    404                     sb.append(',').append(event.values[i]);
    405                 }
    406             }
    407             return sb.append(']').toString();
    408         }
    409     }
    410 
    411     public interface Callback {
    412 
    413         /**
    414          * Called when a sensor requests a pulse
    415          * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP}
    416          * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity.
    417          * @param screenX the location on the screen where the sensor fired or -1
    418          *                if the sensor doesn't support reporting screen locations.
    419          * @param screenY the location on the screen where the sensor fired or -1
    420          *                if the sensor doesn't support reporting screen locations.
    421          */
    422         void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
    423                 float screenX, float screenY);
    424     }
    425 }
    426