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 com.google.common.base.Objects; 20 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.hardware.display.DisplayManager; 24 import android.hardware.display.DisplayManager.DisplayListener; 25 import android.os.PowerManager; 26 import android.telecom.CallAudioState; 27 import android.view.Display; 28 29 import com.android.incallui.AudioModeProvider.AudioModeListener; 30 import com.android.incallui.InCallPresenter.InCallState; 31 import com.android.incallui.InCallPresenter.InCallStateListener; 32 33 /** 34 * Class manages the proximity sensor for the in-call UI. 35 * We enable the proximity sensor while the user in a phone call. The Proximity sensor turns off 36 * the touchscreen and display when the user is close to the screen to prevent user's cheek from 37 * causing touch events. 38 * The class requires special knowledge of the activity and device state to know when the proximity 39 * sensor should be enabled and disabled. Most of that state is fed into this class through 40 * public methods. 41 */ 42 public class ProximitySensor implements AccelerometerListener.OrientationListener, 43 InCallStateListener, AudioModeListener { 44 private static final String TAG = ProximitySensor.class.getSimpleName(); 45 46 private final PowerManager mPowerManager; 47 private final PowerManager.WakeLock mProximityWakeLock; 48 private final AudioModeProvider mAudioModeProvider; 49 private final AccelerometerListener mAccelerometerListener; 50 private final ProximityDisplayListener mDisplayListener; 51 private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN; 52 private boolean mUiShowing = false; 53 private boolean mIsPhoneOffhook = false; 54 private boolean mDialpadVisible; 55 56 // True if the keyboard is currently *not* hidden 57 // Gets updated whenever there is a Configuration change 58 private boolean mIsHardKeyboardOpen; 59 ProximitySensor(Context context, AudioModeProvider audioModeProvider, AccelerometerListener accelerometerListener)60 public ProximitySensor(Context context, AudioModeProvider audioModeProvider, 61 AccelerometerListener accelerometerListener) { 62 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 63 if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { 64 mProximityWakeLock = mPowerManager.newWakeLock( 65 PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); 66 } else { 67 Log.w(TAG, "Device does not support proximity wake lock."); 68 mProximityWakeLock = null; 69 } 70 mAccelerometerListener = accelerometerListener; 71 mAccelerometerListener.setListener(this); 72 73 mDisplayListener = new ProximityDisplayListener( 74 (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE)); 75 mDisplayListener.register(); 76 77 mAudioModeProvider = audioModeProvider; 78 mAudioModeProvider.addListener(this); 79 } 80 tearDown()81 public void tearDown() { 82 mAudioModeProvider.removeListener(this); 83 84 mAccelerometerListener.enable(false); 85 mDisplayListener.unregister(); 86 87 turnOffProximitySensor(true); 88 } 89 90 /** 91 * Called to identify when the device is laid down flat. 92 */ 93 @Override orientationChanged(int orientation)94 public void orientationChanged(int orientation) { 95 mOrientation = orientation; 96 updateProximitySensorMode(); 97 } 98 99 /** 100 * Called to keep track of the overall UI state. 101 */ 102 @Override onStateChange(InCallState oldState, InCallState newState, CallList callList)103 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { 104 // We ignore incoming state because we do not want to enable proximity 105 // sensor during incoming call screen. We check hasLiveCall() because a disconnected call 106 // can also put the in-call screen in the INCALL state. 107 boolean hasOngoingCall = InCallState.INCALL == newState && callList.hasLiveCall(); 108 boolean isOffhook = (InCallState.OUTGOING == newState) || hasOngoingCall; 109 110 if (isOffhook != mIsPhoneOffhook) { 111 mIsPhoneOffhook = isOffhook; 112 113 mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN; 114 mAccelerometerListener.enable(mIsPhoneOffhook); 115 116 updateProximitySensorMode(); 117 } 118 } 119 120 @Override onSupportedAudioMode(int modeMask)121 public void onSupportedAudioMode(int modeMask) { 122 } 123 124 @Override onMute(boolean muted)125 public void onMute(boolean muted) { 126 } 127 128 /** 129 * Called when the audio mode changes during a call. 130 */ 131 @Override onAudioMode(int mode)132 public void onAudioMode(int mode) { 133 updateProximitySensorMode(); 134 } 135 onDialpadVisible(boolean visible)136 public void onDialpadVisible(boolean visible) { 137 mDialpadVisible = visible; 138 updateProximitySensorMode(); 139 } 140 141 /** 142 * Called by InCallActivity to listen for hard keyboard events. 143 */ onConfigurationChanged(Configuration newConfig)144 public void onConfigurationChanged(Configuration newConfig) { 145 mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; 146 147 // Update the Proximity sensor based on keyboard state 148 updateProximitySensorMode(); 149 } 150 151 /** 152 * Used to save when the UI goes in and out of the foreground. 153 */ onInCallShowing(boolean showing)154 public void onInCallShowing(boolean showing) { 155 if (showing) { 156 mUiShowing = true; 157 158 // We only consider the UI not showing for instances where another app took the foreground. 159 // If we stopped showing because the screen is off, we still consider that showing. 160 } else if (mPowerManager.isScreenOn()) { 161 mUiShowing = false; 162 } 163 updateProximitySensorMode(); 164 } 165 onDisplayStateChanged(boolean isDisplayOn)166 void onDisplayStateChanged(boolean isDisplayOn) { 167 Log.i(this, "isDisplayOn: " + isDisplayOn); 168 mAccelerometerListener.enable(isDisplayOn); 169 } 170 171 /** 172 * TODO: There is no way to determine if a screen is off due to proximity or if it is 173 * legitimately off, but if ever we can do that in the future, it would be useful here. 174 * Until then, this function will simply return true of the screen is off. 175 * TODO: Investigate whether this can be replaced with the ProximityDisplayListener. 176 */ isScreenReallyOff()177 public boolean isScreenReallyOff() { 178 return !mPowerManager.isScreenOn(); 179 } 180 turnOnProximitySensor()181 private void turnOnProximitySensor() { 182 if (mProximityWakeLock != null) { 183 if (!mProximityWakeLock.isHeld()) { 184 Log.i(this, "Acquiring proximity wake lock"); 185 mProximityWakeLock.acquire(); 186 } else { 187 Log.i(this, "Proximity wake lock already acquired"); 188 } 189 } 190 } 191 turnOffProximitySensor(boolean screenOnImmediately)192 private void turnOffProximitySensor(boolean screenOnImmediately) { 193 if (mProximityWakeLock != null) { 194 if (mProximityWakeLock.isHeld()) { 195 Log.i(this, "Releasing proximity wake lock"); 196 int flags = 197 (screenOnImmediately ? 0 : PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY); 198 mProximityWakeLock.release(flags); 199 } else { 200 Log.i(this, "Proximity wake lock already released"); 201 } 202 } 203 } 204 205 /** 206 * Updates the wake lock used to control proximity sensor behavior, 207 * based on the current state of the phone. 208 * 209 * On devices that have a proximity sensor, to avoid false touches 210 * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock 211 * whenever the phone is off hook. (When held, that wake lock causes 212 * the screen to turn off automatically when the sensor detects an 213 * object close to the screen.) 214 * 215 * This method is a no-op for devices that don't have a proximity 216 * sensor. 217 * 218 * Proximity wake lock will *not* be held if any one of the 219 * conditions is true while on a call: 220 * 1) If the audio is routed via Bluetooth 221 * 2) If a wired headset is connected 222 * 3) if the speaker is ON 223 * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden) 224 */ updateProximitySensorMode()225 private synchronized void updateProximitySensorMode() { 226 final int audioMode = mAudioModeProvider.getAudioMode(); 227 228 // turn proximity sensor off and turn screen on immediately if 229 // we are using a headset, the keyboard is open, or the device 230 // is being held in a horizontal position. 231 boolean screenOnImmediately = (CallAudioState.ROUTE_WIRED_HEADSET == audioMode 232 || CallAudioState.ROUTE_SPEAKER == audioMode 233 || CallAudioState.ROUTE_BLUETOOTH == audioMode 234 || mIsHardKeyboardOpen); 235 236 // We do not keep the screen off when the user is outside in-call screen and we are 237 // horizontal, but we do not force it on when we become horizontal until the 238 // proximity sensor goes negative. 239 final boolean horizontal = 240 (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL); 241 screenOnImmediately |= !mUiShowing && horizontal; 242 243 // We do not keep the screen off when dialpad is visible, we are horizontal, and 244 // the in-call screen is being shown. 245 // At that moment we're pretty sure users want to use it, instead of letting the 246 // proximity sensor turn off the screen by their hands. 247 screenOnImmediately |= mDialpadVisible && horizontal; 248 249 Log.v(this, "screenonImmediately: ", screenOnImmediately); 250 251 Log.i(this, Objects.toStringHelper(this) 252 .add("keybrd", mIsHardKeyboardOpen ? 1 : 0) 253 .add("dpad", mDialpadVisible ? 1 : 0) 254 .add("offhook", mIsPhoneOffhook ? 1 : 0) 255 .add("hor", horizontal ? 1 : 0) 256 .add("ui", mUiShowing ? 1 : 0) 257 .add("aud", CallAudioState.audioRouteToString(audioMode)) 258 .toString()); 259 260 if (mIsPhoneOffhook && !screenOnImmediately) { 261 Log.d(this, "Turning on proximity sensor"); 262 // Phone is in use! Arrange for the screen to turn off 263 // automatically when the sensor detects a close object. 264 turnOnProximitySensor(); 265 } else { 266 Log.d(this, "Turning off proximity sensor"); 267 // Phone is either idle, or ringing. We don't want any special proximity sensor 268 // behavior in either case. 269 turnOffProximitySensor(screenOnImmediately); 270 } 271 } 272 273 /** 274 * Implementation of a {@link DisplayListener} that maintains a binary state: 275 * Screen on vs screen off. Used by the proximity sensor manager to decide whether or not 276 * it needs to listen to accelerometer events. 277 */ 278 public class ProximityDisplayListener implements DisplayListener { 279 private DisplayManager mDisplayManager; 280 private boolean mIsDisplayOn = true; 281 ProximityDisplayListener(DisplayManager displayManager)282 ProximityDisplayListener(DisplayManager displayManager) { 283 mDisplayManager = displayManager; 284 } 285 register()286 void register() { 287 mDisplayManager.registerDisplayListener(this, null); 288 } 289 unregister()290 void unregister() { 291 mDisplayManager.unregisterDisplayListener(this); 292 } 293 294 @Override onDisplayRemoved(int displayId)295 public void onDisplayRemoved(int displayId) { 296 } 297 298 @Override onDisplayChanged(int displayId)299 public void onDisplayChanged(int displayId) { 300 if (displayId == Display.DEFAULT_DISPLAY) { 301 final Display display = mDisplayManager.getDisplay(displayId); 302 303 final boolean isDisplayOn = display.getState() != Display.STATE_OFF; 304 // For call purposes, we assume that as long as the screen is not truly off, it is 305 // considered on, even if it is in an unknown or low power idle state. 306 if (isDisplayOn != mIsDisplayOn) { 307 mIsDisplayOn = isDisplayOn; 308 onDisplayStateChanged(mIsDisplayOn); 309 } 310 } 311 } 312 313 @Override onDisplayAdded(int displayId)314 public void onDisplayAdded(int displayId) { 315 } 316 } 317 } 318