ProximitySensor.java revision 4ed8d8d7d2c6766e3712a407551f79322e544aa8
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 android.telecomm.CallAudioState;
24
25import com.android.incallui.AudioModeProvider.AudioModeListener;
26import com.android.incallui.InCallPresenter.InCallState;
27import com.android.incallui.InCallPresenter.InCallStateListener;
28import com.google.common.base.Objects;
29
30/**
31 * Class manages the proximity sensor for the in-call UI.
32 * We enable the proximity sensor while the user in a phone call. The Proximity sensor turns off
33 * the touchscreen and display when the user is close to the screen to prevent user's cheek from
34 * causing touch events.
35 * The class requires special knowledge of the activity and device state to know when the proximity
36 * sensor should be enabled and disabled. Most of that state is fed into this class through
37 * public methods.
38 */
39public class ProximitySensor implements AccelerometerListener.OrientationListener,
40        InCallStateListener, AudioModeListener {
41    private static final String TAG = ProximitySensor.class.getSimpleName();
42
43    private final PowerManager mPowerManager;
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        mAccelerometerListener = new AccelerometerListener(context, this);
58        mAudioModeProvider = audioModeProvider;
59        mAudioModeProvider.addListener(this);
60    }
61
62    public void tearDown() {
63        mAudioModeProvider.removeListener(this);
64
65        mAccelerometerListener.enable(false);
66
67        TelecommAdapter.getInstance().turnOffProximitySensor(true);
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        boolean isOffhook = (InCallState.INCALL == state
87                || InCallState.OUTGOING == state);
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 = (CallAudioState.ROUTE_WIRED_HEADSET == audioMode
181                    || CallAudioState.ROUTE_SPEAKER == audioMode
182                    || CallAudioState.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", CallAudioState.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                TelecommAdapter.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                TelecommAdapter.getInstance().turnOffProximitySensor(screenOnImmediately);
219            }
220        }
221}
222