Home | History | Annotate | Download | only in incallui
      1 /*
      2  * Copyright (C) 2013 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.incallui;
     18 
     19 import android.content.Context;
     20 import android.content.res.Configuration;
     21 import android.os.PowerManager;
     22 
     23 import com.android.incallui.AudioModeProvider.AudioModeListener;
     24 import com.android.incallui.InCallPresenter.InCallState;
     25 import com.android.incallui.InCallPresenter.InCallStateListener;
     26 import com.android.services.telephony.common.AudioMode;
     27 import com.google.common.base.Objects;
     28 
     29 /**
     30  * Class manages the proximity sensor for the in-call UI.
     31  * We enable the proximity sensor while the user in a phone call. The Proximity sensor turns off
     32  * the touchscreen and display when the user is close to the screen to prevent user's cheek from
     33  * causing touch events.
     34  * The class requires special knowledge of the activity and device state to know when the proximity
     35  * sensor should be enabled and disabled. Most of that state is fed into this class through
     36  * public methods.
     37  */
     38 public class ProximitySensor implements AccelerometerListener.OrientationListener,
     39         InCallStateListener, AudioModeListener {
     40     private static final String TAG = ProximitySensor.class.getSimpleName();
     41 
     42     private final PowerManager mPowerManager;
     43     private final PowerManager.WakeLock mProximityWakeLock;
     44     private final AudioModeProvider mAudioModeProvider;
     45     private final AccelerometerListener mAccelerometerListener;
     46     private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
     47     private boolean mUiShowing = false;
     48     private boolean mIsPhoneOffhook = false;
     49     private boolean mDialpadVisible;
     50 
     51     // True if the keyboard is currently *not* hidden
     52     // Gets updated whenever there is a Configuration change
     53     private boolean mIsHardKeyboardOpen;
     54 
     55     public ProximitySensor(Context context, AudioModeProvider audioModeProvider) {
     56         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
     57 
     58         if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
     59             mProximityWakeLock = mPowerManager.newWakeLock(
     60                     PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
     61         } else {
     62             mProximityWakeLock = null;
     63         }
     64         Log.d(this, "onCreate: mProximityWakeLock: ", mProximityWakeLock);
     65 
     66         mAccelerometerListener = new AccelerometerListener(context, this);
     67         mAudioModeProvider = audioModeProvider;
     68         mAudioModeProvider.addListener(this);
     69     }
     70 
     71     public void tearDown() {
     72         mAudioModeProvider.removeListener(this);
     73 
     74         mAccelerometerListener.enable(false);
     75 
     76         if (mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
     77             mProximityWakeLock.release();
     78         }
     79     }
     80 
     81     /**
     82      * Called to identify when the device is laid down flat.
     83      */
     84     @Override
     85     public void orientationChanged(int orientation) {
     86         mOrientation = orientation;
     87         updateProximitySensorMode();
     88     }
     89 
     90     /**
     91      * Called to keep track of the overall UI state.
     92      */
     93     @Override
     94     public void onStateChange(InCallState state, CallList callList) {
     95         // We ignore incoming state because we do not want to enable proximity
     96         // sensor during incoming call screen
     97         boolean isOffhook = (InCallState.INCALL == state
     98                 || InCallState.OUTGOING == state);
     99 
    100         if (isOffhook != mIsPhoneOffhook) {
    101             mIsPhoneOffhook = isOffhook;
    102 
    103             mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
    104             mAccelerometerListener.enable(mIsPhoneOffhook);
    105 
    106             updateProximitySensorMode();
    107         }
    108     }
    109 
    110     @Override
    111     public void onSupportedAudioMode(int modeMask) {
    112     }
    113 
    114     @Override
    115     public void onMute(boolean muted) {
    116     }
    117 
    118     /**
    119      * Called when the audio mode changes during a call.
    120      */
    121     @Override
    122     public void onAudioMode(int mode) {
    123         updateProximitySensorMode();
    124     }
    125 
    126     public void onDialpadVisible(boolean visible) {
    127         mDialpadVisible = visible;
    128         updateProximitySensorMode();
    129     }
    130 
    131     /**
    132      * Called by InCallActivity to listen for hard keyboard events.
    133      */
    134     public void onConfigurationChanged(Configuration newConfig) {
    135         mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
    136 
    137         // Update the Proximity sensor based on keyboard state
    138         updateProximitySensorMode();
    139     }
    140 
    141     /**
    142      * Used to save when the UI goes in and out of the foreground.
    143      */
    144     public void onInCallShowing(boolean showing) {
    145         if (showing) {
    146             mUiShowing = true;
    147 
    148         // We only consider the UI not showing for instances where another app took the foreground.
    149         // If we stopped showing because the screen is off, we still consider that showing.
    150         } else if (mPowerManager.isScreenOn()) {
    151             mUiShowing = false;
    152         }
    153         updateProximitySensorMode();
    154     }
    155 
    156     /**
    157      * TODO: There is no way to determine if a screen is off due to proximity or if it is
    158      * legitimately off, but if ever we can do that in the future, it would be useful here.
    159      * Until then, this function will simply return true of the screen is off.
    160      */
    161     public boolean isScreenReallyOff() {
    162         return !mPowerManager.isScreenOn();
    163     }
    164 
    165     /**
    166      * @return true if this device supports the "proximity sensor
    167      * auto-lock" feature while in-call (see updateProximitySensorMode()).
    168      */
    169     private boolean proximitySensorModeEnabled() {
    170         // TODO: Do we disable notification's expanded view when app is in foreground and
    171         // proximity sensor is on? Is it even possible to do this any more?
    172         return (mProximityWakeLock != null);
    173     }
    174 
    175     /**
    176      * Updates the wake lock used to control proximity sensor behavior,
    177      * based on the current state of the phone.
    178      *
    179      * On devices that have a proximity sensor, to avoid false touches
    180      * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock
    181      * whenever the phone is off hook.  (When held, that wake lock causes
    182      * the screen to turn off automatically when the sensor detects an
    183      * object close to the screen.)
    184      *
    185      * This method is a no-op for devices that don't have a proximity
    186      * sensor.
    187      *
    188      * Proximity wake lock will *not* be held if any one of the
    189      * conditions is true while on a call:
    190      * 1) If the audio is routed via Bluetooth
    191      * 2) If a wired headset is connected
    192      * 3) if the speaker is ON
    193      * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden)
    194      */
    195     private void updateProximitySensorMode() {
    196         if (proximitySensorModeEnabled()) {
    197             synchronized (mProximityWakeLock) {
    198 
    199                 final int audioMode = mAudioModeProvider.getAudioMode();
    200 
    201                 // turn proximity sensor off and turn screen on immediately if
    202                 // we are using a headset, the keyboard is open, or the device
    203                 // is being held in a horizontal position.
    204                 boolean screenOnImmediately = (AudioMode.WIRED_HEADSET == audioMode
    205                         || AudioMode.SPEAKER == audioMode
    206                         || AudioMode.BLUETOOTH == audioMode
    207                         || mIsHardKeyboardOpen);
    208 
    209                 // We do not keep the screen off when the user is outside in-call screen and we are
    210                 // horizontal, but we do not force it on when we become horizontal until the
    211                 // proximity sensor goes negative.
    212                 final boolean horizontal =
    213                         (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
    214                 screenOnImmediately |= !mUiShowing && horizontal;
    215 
    216                 // We do not keep the screen off when dialpad is visible, we are horizontal, and
    217                 // the in-call screen is being shown.
    218                 // At that moment we're pretty sure users want to use it, instead of letting the
    219                 // proximity sensor turn off the screen by their hands.
    220                 screenOnImmediately |= mDialpadVisible && horizontal;
    221 
    222                 Log.v(this, "screenonImmediately: ", screenOnImmediately);
    223 
    224                 Log.i(this, Objects.toStringHelper(this)
    225                         .add("keybrd", mIsHardKeyboardOpen ? 1 : 0)
    226                         .add("dpad", mDialpadVisible ? 1 : 0)
    227                         .add("offhook", mIsPhoneOffhook ? 1 : 0)
    228                         .add("hor", horizontal ? 1 : 0)
    229                         .add("ui", mUiShowing ? 1 : 0)
    230                         .add("aud", AudioMode.toString(audioMode)).toString());
    231 
    232                 if (mIsPhoneOffhook && !screenOnImmediately) {
    233                     final String logStr = "turning on proximity sensor: ";
    234                     // Phone is in use!  Arrange for the screen to turn off
    235                     // automatically when the sensor detects a close object.
    236                     if (!mProximityWakeLock.isHeld()) {
    237                         Log.i(this, logStr + "acquiring");
    238                         mProximityWakeLock.acquire();
    239                     } else {
    240                         Log.i(this, logStr + "already acquired");
    241                     }
    242                 } else {
    243                     final String logStr = "turning off proximity sensor: ";
    244                     // Phone is either idle, or ringing.  We don't want any
    245                     // special proximity sensor behavior in either case.
    246                     if (mProximityWakeLock.isHeld()) {
    247                         Log.i(this, logStr + "releasing");
    248                         // Wait until user has moved the phone away from his head if we are
    249                         // releasing due to the phone call ending.
    250                         // Qtherwise, turn screen on immediately
    251                         int flags =
    252                             (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
    253                         mProximityWakeLock.release(flags);
    254                     } else {
    255                         Log.i(this, logStr + "already released");
    256                     }
    257                 }
    258             }
    259         }
    260     }
    261 
    262 }
    263