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             mCurrentlyFar = event.values[0] >= event.sensor.getMaximumRange();
    259             mProxCallback.accept(mCurrentlyFar);
    260 
    261             long now = SystemClock.elapsedRealtime();
    262             if (mCurrentlyFar == null) {
    263                 // Sensor has been unregistered by the proxCallback. Do nothing.
    264             } else if (!mCurrentlyFar) {
    265                 mLastNear = now;
    266             } else if (mCurrentlyFar && now - mLastNear < mPolicy.proxCooldownTriggerMs) {
    267                 // If the last near was very recent, we might be using more power for prox
    268                 // wakeups than we're saving from turning of the screen. Instead, turn it off
    269                 // for a while.
    270                 mCooldownTimer.schedule(mPolicy.proxCooldownPeriodMs,
    271                         AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
    272                 updateRegistered();
    273             }
    274         }
    275 
    276         @Override
    277         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    278         }
    279 
    280         @Override
    281         public String toString() {
    282             return String.format("{registered=%s, requested=%s, coolingDown=%s, currentlyFar=%s}",
    283                     mRegistered, mRequested, mCooldownTimer.isScheduled(), mCurrentlyFar);
    284         }
    285     }
    286 
    287     private class TriggerSensor extends TriggerEventListener {
    288         final Sensor mSensor;
    289         final boolean mConfigured;
    290         final int mPulseReason;
    291         final String mSetting;
    292         final boolean mReportsTouchCoordinates;
    293         final boolean mSettingDefault;
    294         final boolean mRequiresTouchscreen;
    295 
    296         private boolean mRequested;
    297         private boolean mRegistered;
    298         private boolean mDisabled;
    299 
    300         public TriggerSensor(Sensor sensor, String setting, boolean configured, int pulseReason,
    301                 boolean reportsTouchCoordinates, boolean requiresTouchscreen) {
    302             this(sensor, setting, true /* settingDef */, configured, pulseReason,
    303                     reportsTouchCoordinates, requiresTouchscreen);
    304         }
    305 
    306         public TriggerSensor(Sensor sensor, String setting, boolean settingDef,
    307                 boolean configured, int pulseReason, boolean reportsTouchCoordinates,
    308                 boolean requiresTouchscreen) {
    309             mSensor = sensor;
    310             mSetting = setting;
    311             mSettingDefault = settingDef;
    312             mConfigured = configured;
    313             mPulseReason = pulseReason;
    314             mReportsTouchCoordinates = reportsTouchCoordinates;
    315             mRequiresTouchscreen = requiresTouchscreen;
    316         }
    317 
    318         public void setListening(boolean listen) {
    319             if (mRequested == listen) return;
    320             mRequested = listen;
    321             updateListener();
    322         }
    323 
    324         public void setDisabled(boolean disabled) {
    325             if (mDisabled == disabled) return;
    326             mDisabled = disabled;
    327             updateListener();
    328         }
    329 
    330         public void updateListener() {
    331             if (!mConfigured || mSensor == null) return;
    332             if (mRequested && !mDisabled && enabledBySetting() && !mRegistered) {
    333                 mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
    334                 if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
    335             } else if (mRegistered) {
    336                 final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
    337                 if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
    338                 mRegistered = false;
    339             }
    340         }
    341 
    342         private boolean enabledBySetting() {
    343             if (TextUtils.isEmpty(mSetting)) {
    344                 return true;
    345             }
    346             return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0,
    347                     UserHandle.USER_CURRENT) != 0;
    348         }
    349 
    350         @Override
    351         public String toString() {
    352             return new StringBuilder("{mRegistered=").append(mRegistered)
    353                     .append(", mRequested=").append(mRequested)
    354                     .append(", mDisabled=").append(mDisabled)
    355                     .append(", mConfigured=").append(mConfigured)
    356                     .append(", mSensor=").append(mSensor).append("}").toString();
    357         }
    358 
    359         @Override
    360         @AnyThread
    361         public void onTrigger(TriggerEvent event) {
    362             DozeLog.traceSensor(mContext, mPulseReason);
    363             mHandler.post(mWakeLock.wrap(() -> {
    364                 if (DEBUG) Log.d(TAG, "onTrigger: " + triggerEventToString(event));
    365                 boolean sensorPerformsProxCheck = false;
    366                 if (mSensor.getType() == Sensor.TYPE_PICK_UP_GESTURE) {
    367                     int subType = (int) event.values[0];
    368                     MetricsLogger.action(
    369                             mContext, MetricsProto.MetricsEvent.ACTION_AMBIENT_GESTURE,
    370                             subType);
    371                     sensorPerformsProxCheck =
    372                             mDozeParameters.getPickupSubtypePerformsProxCheck(subType);
    373                 }
    374 
    375                 mRegistered = false;
    376                 float screenX = -1;
    377                 float screenY = -1;
    378                 if (mReportsTouchCoordinates && event.values.length >= 2) {
    379                     screenX = event.values[0];
    380                     screenY = event.values[1];
    381                 }
    382                 mCallback.onSensorPulse(mPulseReason, sensorPerformsProxCheck, screenX, screenY);
    383                 updateListener();  // reregister, this sensor only fires once
    384             }));
    385         }
    386 
    387         public void registerSettingsObserver(ContentObserver settingsObserver) {
    388             if (mConfigured && !TextUtils.isEmpty(mSetting)) {
    389                 mResolver.registerContentObserver(
    390                         Settings.Secure.getUriFor(mSetting), false /* descendants */,
    391                         mSettingsObserver, UserHandle.USER_ALL);
    392             }
    393         }
    394 
    395         private String triggerEventToString(TriggerEvent event) {
    396             if (event == null) return null;
    397             final StringBuilder sb = new StringBuilder("TriggerEvent[")
    398                     .append(event.timestamp).append(',')
    399                     .append(event.sensor.getName());
    400             if (event.values != null) {
    401                 for (int i = 0; i < event.values.length; i++) {
    402                     sb.append(',').append(event.values[i]);
    403                 }
    404             }
    405             return sb.append(']').toString();
    406         }
    407     }
    408 
    409     public interface Callback {
    410 
    411         /**
    412          * Called when a sensor requests a pulse
    413          * @param pulseReason Requesting sensor, e.g. {@link DozeLog#PULSE_REASON_SENSOR_PICKUP}
    414          * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity.
    415          * @param screenX the location on the screen where the sensor fired or -1
    416          *                if the sensor doesn't support reporting screen locations.
    417          * @param screenY the location on the screen where the sensor fired or -1
    418          *                if the sensor doesn't support reporting screen locations.
    419          */
    420         void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
    421                 float screenX, float screenY);
    422     }
    423 }
    424