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