• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ProximitySensor(Context context, AudioModeProvider audioModeProvider)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 
tearDown()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
orientationChanged(int orientation)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
onStateChange(InCallState oldState, InCallState newState, CallList callList)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
onSupportedAudioMode(int modeMask)100     public void onSupportedAudioMode(int modeMask) {
101     }
102 
103     @Override
onMute(boolean muted)104     public void onMute(boolean muted) {
105     }
106 
107     /**
108      * Called when the audio mode changes during a call.
109      */
110     @Override
onAudioMode(int mode)111     public void onAudioMode(int mode) {
112         updateProximitySensorMode();
113     }
114 
onDialpadVisible(boolean visible)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      */
onConfigurationChanged(Configuration newConfig)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      */
onInCallShowing(boolean showing)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      */
isScreenReallyOff()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      */
updateProximitySensorMode()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