ProximitySensor.java revision 11e202f49d4382bbf6271e90421c5db333f3b346
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
17package com.android.incallui;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.os.PowerManager;
22
23import com.android.incallui.AudioModeProvider.AudioModeListener;
24import com.android.incallui.InCallPresenter.InCallState;
25import com.android.incallui.InCallPresenter.InCallStateListener;
26import com.android.services.telephony.common.AudioMode;
27import 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 */
38public 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