ProximitySensor.java revision 3c2a439eb67ec6fb89daf1914ab3ce05e8aaa428
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; 27 28/** 29 * Class manages the proximity sensor for the in-call UI. 30 * We enable the proximity sensor while the user in a phone call. The Proximity sensor turns off 31 * the touchscreen and display when the user is close to the screen to prevent user's cheek from 32 * causing touch events. 33 * The class requires special knowledge of the activity and device state to know when the proximity 34 * sensor should be enabled and disabled. Most of that state is fed into this class through 35 * public methods. 36 */ 37public class ProximitySensor implements AccelerometerListener.OrientationListener, 38 InCallStateListener, AudioModeListener { 39 private static final String TAG = ProximitySensor.class.getSimpleName(); 40 41 private final PowerManager mPowerManager; 42 private final PowerManager.WakeLock mProximityWakeLock; 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 54 public ProximitySensor(Context context, AudioModeProvider audioModeProvider) { 55 mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 56 57 if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { 58 mProximityWakeLock = mPowerManager.newWakeLock( 59 PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); 60 } else { 61 mProximityWakeLock = null; 62 } 63 Logger.d(this, "onCreate: mProximityWakeLock: ", mProximityWakeLock); 64 65 mAccelerometerListener = new AccelerometerListener(context, this); 66 mAudioModeProvider = audioModeProvider; 67 mAudioModeProvider.addListener(this); 68 } 69 70 /** 71 * Called to identify when the device is laid down flat. 72 */ 73 @Override 74 public void orientationChanged(int orientation) { 75 mOrientation = orientation; 76 updateProximitySensorMode(); 77 } 78 79 /** 80 * Called to keep track of the overall UI state. 81 */ 82 @Override 83 public void onStateChange(InCallState state, CallList callList) { 84 // We ignore incoming state because we do not want to enable proximity 85 // sensor during incoming call screen 86 mIsPhoneOffhook = (InCallState.INCALL == state 87 || InCallState.OUTGOING == state); 88 89 mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN; 90 mAccelerometerListener.enable(mIsPhoneOffhook); 91 92 updateProximitySensorMode(); 93 } 94 95 @Override 96 public void onSupportedAudioMode(int modeMask) { 97 } 98 99 /** 100 * Called when the audio mode changes during a call. 101 */ 102 @Override 103 public void onAudioMode(int mode) { 104 updateProximitySensorMode(); 105 } 106 107 public void onDialpadVisible(boolean visible) { 108 mDialpadVisible = visible; 109 updateProximitySensorMode(); 110 } 111 112 /** 113 * Called by InCallActivity to listen for hard keyboard events. 114 */ 115 public void onConfigurationChanged(Configuration newConfig) { 116 mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; 117 118 // Update the Proximity sensor based on keyboard state 119 updateProximitySensorMode(); 120 } 121 122 /** 123 * Used to save when the UI goes in and out of the foreground. 124 */ 125 public void onInCallShowing(boolean showing) { 126 if (showing) { 127 mUiShowing = true; 128 129 // We only consider the UI not showing for instances where another app took the foreground. 130 // If we stopped showing because the screen is off, we still consider that showing. 131 } else if (mPowerManager.isScreenOn()) { 132 mUiShowing = false; 133 } 134 updateProximitySensorMode(); 135 } 136 137 /** 138 * @return true if this device supports the "proximity sensor 139 * auto-lock" feature while in-call (see updateProximitySensorMode()). 140 */ 141 private boolean proximitySensorModeEnabled() { 142 // TODO(klp): Do we disable notification's expanded view when app is in foreground and 143 // proximity sensor is on? Is it even possible to do this any more? 144 return (mProximityWakeLock != null); 145 } 146 147 /** 148 * Updates the wake lock used to control proximity sensor behavior, 149 * based on the current state of the phone. 150 * 151 * On devices that have a proximity sensor, to avoid false touches 152 * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock 153 * whenever the phone is off hook. (When held, that wake lock causes 154 * the screen to turn off automatically when the sensor detects an 155 * object close to the screen.) 156 * 157 * This method is a no-op for devices that don't have a proximity 158 * sensor. 159 * 160 * Proximity wake lock will *not* be held if any one of the 161 * conditions is true while on a call: 162 * 1) If the audio is routed via Bluetooth 163 * 2) If a wired headset is connected 164 * 3) if the speaker is ON 165 * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden) 166 */ 167 private void updateProximitySensorMode() { 168 Logger.v(this, "updateProximitySensorMode"); 169 170 if (proximitySensorModeEnabled()) { 171 Logger.v(this, "keyboard open: ", mIsHardKeyboardOpen); 172 Logger.v(this, "dialpad visible: ", mDialpadVisible); 173 Logger.v(this, "isOffhook: ", mIsPhoneOffhook); 174 175 synchronized (mProximityWakeLock) { 176 177 final int audioMode = mAudioModeProvider.getAudioMode(); 178 Logger.v(this, "audioMode: ", AudioMode.toString(audioMode)); 179 180 // turn proximity sensor off and turn screen on immediately if 181 // we are using a headset, the keyboard is open, or the device 182 // is being held in a horizontal position. 183 boolean screenOnImmediately = (AudioMode.WIRED_HEADSET == audioMode 184 || AudioMode.SPEAKER == audioMode 185 || AudioMode.BLUETOOTH == audioMode 186 || mIsHardKeyboardOpen); 187 188 // We do not keep the screen off when the user is outside in-call screen and we are 189 // horizontal, but we do not force it on when we become horizontal until the 190 // proximity sensor goes negative. 191 final boolean horizontal = 192 (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL); 193 Logger.v(this, "horizontal: ", horizontal); 194 screenOnImmediately |= !mUiShowing && horizontal; 195 196 // We do not keep the screen off when dialpad is visible, we are horizontal, and 197 // the in-call screen is being shown. 198 // At that moment we're pretty sure users want to use it, instead of letting the 199 // proximity sensor turn off the screen by their hands. 200 screenOnImmediately |= mDialpadVisible && horizontal; 201 202 Logger.v(this, "screenonImmediately: ", screenOnImmediately); 203 204 if (mIsPhoneOffhook && !screenOnImmediately) { 205 // Phone is in use! Arrange for the screen to turn off 206 // automatically when the sensor detects a close object. 207 if (!mProximityWakeLock.isHeld()) { 208 Logger.d(this, "updateProximitySensorMode: acquiring..."); 209 mProximityWakeLock.acquire(); 210 } else { 211 Logger.v(this, "updateProximitySensorMode: lock already held."); 212 } 213 } else { 214 // Phone is either idle, or ringing. We don't want any 215 // special proximity sensor behavior in either case. 216 if (mProximityWakeLock.isHeld()) { 217 Logger.d(this, "updateProximitySensorMode: releasing..."); 218 // Wait until user has moved the phone away from his head if we are 219 // releasing due to the phone call ending. 220 // Qtherwise, turn screen on immediately 221 int flags = 222 (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE); 223 mProximityWakeLock.release(flags); 224 } else { 225 Logger.v(this, "updateProximitySensorMode: lock already released."); 226 } 227 } 228 } 229 } 230 } 231 232} 233