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