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 import android.telecom.AudioState;
     23 
     24 import com.android.incallui.AudioModeProvider.AudioModeListener;
     25 import com.android.incallui.InCallPresenter.InCallState;
     26 import com.android.incallui.InCallPresenter.InCallStateListener;
     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 AudioModeProvider mAudioModeProvider;
     44     private final AccelerometerListener mAccelerometerListener;
     45     private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
     46     private boolean mUiShowing = false;
     47     private boolean mIsPhoneOffhook = false;
     48     private boolean mDialpadVisible;
     49 
     50     // True if the keyboard is currently *not* hidden
     51     // Gets updated whenever there is a Configuration change
     52     private boolean mIsHardKeyboardOpen;
     53 
     54     public ProximitySensor(Context context, AudioModeProvider audioModeProvider) {
     55         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
     56         mAccelerometerListener = new AccelerometerListener(context, this);
     57         mAudioModeProvider = audioModeProvider;
     58         mAudioModeProvider.addListener(this);
     59     }
     60 
     61     public void tearDown() {
     62         mAudioModeProvider.removeListener(this);
     63 
     64         mAccelerometerListener.enable(false);
     65 
     66         TelecomAdapter.getInstance().turnOffProximitySensor(true);
     67     }
     68 
     69     /**
     70      * Called to identify when the device is laid down flat.
     71      */
     72     @Override
     73     public void orientationChanged(int orientation) {
     74         mOrientation = orientation;
     75         updateProximitySensorMode();
     76     }
     77 
     78     /**
     79      * Called to keep track of the overall UI state.
     80      */
     81     @Override
     82     public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
     83         // We ignore incoming state because we do not want to enable proximity
     84         // sensor during incoming call screen. We check hasLiveCall() because a disconnected call
     85         // can also put the in-call screen in the INCALL state.
     86         boolean hasOngoingCall = InCallState.INCALL == newState && callList.hasLiveCall();
     87         boolean isOffhook = (InCallState.OUTGOING == newState) || hasOngoingCall;
     88 
     89         if (isOffhook != mIsPhoneOffhook) {
     90             mIsPhoneOffhook = isOffhook;
     91 
     92             mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
     93             mAccelerometerListener.enable(mIsPhoneOffhook);
     94 
     95             updateProximitySensorMode();
     96         }
     97     }
     98 
     99     @Override
    100     public void onSupportedAudioMode(int modeMask) {
    101     }
    102 
    103     @Override
    104     public void onMute(boolean muted) {
    105     }
    106 
    107     /**
    108      * Called when the audio mode changes during a call.
    109      */
    110     @Override
    111     public void onAudioMode(int mode) {
    112         updateProximitySensorMode();
    113     }
    114 
    115     public void onDialpadVisible(boolean visible) {
    116         mDialpadVisible = visible;
    117         updateProximitySensorMode();
    118     }
    119 
    120     /**
    121      * Called by InCallActivity to listen for hard keyboard events.
    122      */
    123     public void onConfigurationChanged(Configuration newConfig) {
    124         mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
    125 
    126         // Update the Proximity sensor based on keyboard state
    127         updateProximitySensorMode();
    128     }
    129 
    130     /**
    131      * Used to save when the UI goes in and out of the foreground.
    132      */
    133     public void onInCallShowing(boolean showing) {
    134         if (showing) {
    135             mUiShowing = true;
    136 
    137         // We only consider the UI not showing for instances where another app took the foreground.
    138         // If we stopped showing because the screen is off, we still consider that showing.
    139         } else if (mPowerManager.isScreenOn()) {
    140             mUiShowing = false;
    141         }
    142         updateProximitySensorMode();
    143     }
    144 
    145     /**
    146      * TODO: There is no way to determine if a screen is off due to proximity or if it is
    147      * legitimately off, but if ever we can do that in the future, it would be useful here.
    148      * Until then, this function will simply return true of the screen is off.
    149      */
    150     public boolean isScreenReallyOff() {
    151         return !mPowerManager.isScreenOn();
    152     }
    153 
    154     /**
    155      * Updates the wake lock used to control proximity sensor behavior,
    156      * based on the current state of the phone.
    157      *
    158      * On devices that have a proximity sensor, to avoid false touches
    159      * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock
    160      * whenever the phone is off hook.  (When held, that wake lock causes
    161      * the screen to turn off automatically when the sensor detects an
    162      * object close to the screen.)
    163      *
    164      * This method is a no-op for devices that don't have a proximity
    165      * sensor.
    166      *
    167      * Proximity wake lock will *not* be held if any one of the
    168      * conditions is true while on a call:
    169      * 1) If the audio is routed via Bluetooth
    170      * 2) If a wired headset is connected
    171      * 3) if the speaker is ON
    172      * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden)
    173      */
    174     private synchronized void updateProximitySensorMode() {
    175         final int audioMode = mAudioModeProvider.getAudioMode();
    176 
    177         // turn proximity sensor off and turn screen on immediately if
    178         // we are using a headset, the keyboard is open, or the device
    179         // is being held in a horizontal position.
    180             boolean screenOnImmediately = (AudioState.ROUTE_WIRED_HEADSET == audioMode
    181                     || AudioState.ROUTE_SPEAKER == audioMode
    182                     || AudioState.ROUTE_BLUETOOTH == audioMode
    183                     || mIsHardKeyboardOpen);
    184 
    185             // We do not keep the screen off when the user is outside in-call screen and we are
    186             // horizontal, but we do not force it on when we become horizontal until the
    187             // proximity sensor goes negative.
    188             final boolean horizontal =
    189                     (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
    190             screenOnImmediately |= !mUiShowing && horizontal;
    191 
    192             // We do not keep the screen off when dialpad is visible, we are horizontal, and
    193             // the in-call screen is being shown.
    194             // At that moment we're pretty sure users want to use it, instead of letting the
    195             // proximity sensor turn off the screen by their hands.
    196             screenOnImmediately |= mDialpadVisible && horizontal;
    197 
    198             Log.v(this, "screenonImmediately: ", screenOnImmediately);
    199 
    200             Log.i(this, Objects.toStringHelper(this)
    201                     .add("keybrd", mIsHardKeyboardOpen ? 1 : 0)
    202                     .add("dpad", mDialpadVisible ? 1 : 0)
    203                     .add("offhook", mIsPhoneOffhook ? 1 : 0)
    204                     .add("hor", horizontal ? 1 : 0)
    205                     .add("ui", mUiShowing ? 1 : 0)
    206                     .add("aud", AudioState.audioRouteToString(audioMode))
    207                     .toString());
    208 
    209             if (mIsPhoneOffhook && !screenOnImmediately) {
    210                 Log.d(this, "Turning on proximity sensor");
    211                 // Phone is in use!  Arrange for the screen to turn off
    212                 // automatically when the sensor detects a close object.
    213                 TelecomAdapter.getInstance().turnOnProximitySensor();
    214             } else {
    215                 Log.d(this, "Turning off proximity sensor");
    216                 // Phone is either idle, or ringing.  We don't want any special proximity sensor
    217                 // behavior in either case.
    218                 TelecomAdapter.getInstance().turnOffProximitySensor(screenOnImmediately);
    219             }
    220         }
    221 }
    222