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