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