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; 22import android.telecom.AudioState; 23 24import com.android.incallui.AudioModeProvider.AudioModeListener; 25import com.android.incallui.InCallPresenter.InCallState; 26import com.android.incallui.InCallPresenter.InCallStateListener; 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 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 mAccelerometerListener = new AccelerometerListener(context, this); 57 mAudioModeProvider = audioModeProvider; 58 mAudioModeProvider.addListener(this); 59 } 60 61 public void tearDown() { 62 mAudioModeProvider.removeListener(this); 63 64 mAccelerometerListener.enable(false); 65 66 TelecomAdapter.getInstance().turnOffProximitySensor(true); 67 } 68 69 /** 70 * Called to identify when the device is laid down flat. 71 */ 72 @Override 73 public void orientationChanged(int orientation) { 74 mOrientation = orientation; 75 updateProximitySensorMode(); 76 } 77 78 /** 79 * Called to keep track of the overall UI state. 80 */ 81 @Override 82 public void onStateChange(InCallState oldState, InCallState newState, CallList callList) { 83 // We ignore incoming state because we do not want to enable proximity 84 // sensor during incoming call screen. We check hasLiveCall() because a disconnected call 85 // can also put the in-call screen in the INCALL state. 86 boolean hasOngoingCall = InCallState.INCALL == newState && callList.hasLiveCall(); 87 boolean isOffhook = (InCallState.OUTGOING == newState) || hasOngoingCall; 88 89 if (isOffhook != mIsPhoneOffhook) { 90 mIsPhoneOffhook = isOffhook; 91 92 mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN; 93 mAccelerometerListener.enable(mIsPhoneOffhook); 94 95 updateProximitySensorMode(); 96 } 97 } 98 99 @Override 100 public void onSupportedAudioMode(int modeMask) { 101 } 102 103 @Override 104 public void onMute(boolean muted) { 105 } 106 107 /** 108 * Called when the audio mode changes during a call. 109 */ 110 @Override 111 public void onAudioMode(int mode) { 112 updateProximitySensorMode(); 113 } 114 115 public void onDialpadVisible(boolean visible) { 116 mDialpadVisible = visible; 117 updateProximitySensorMode(); 118 } 119 120 /** 121 * Called by InCallActivity to listen for hard keyboard events. 122 */ 123 public void onConfigurationChanged(Configuration newConfig) { 124 mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; 125 126 // Update the Proximity sensor based on keyboard state 127 updateProximitySensorMode(); 128 } 129 130 /** 131 * Used to save when the UI goes in and out of the foreground. 132 */ 133 public void onInCallShowing(boolean showing) { 134 if (showing) { 135 mUiShowing = true; 136 137 // We only consider the UI not showing for instances where another app took the foreground. 138 // If we stopped showing because the screen is off, we still consider that showing. 139 } else if (mPowerManager.isScreenOn()) { 140 mUiShowing = false; 141 } 142 updateProximitySensorMode(); 143 } 144 145 /** 146 * TODO: There is no way to determine if a screen is off due to proximity or if it is 147 * legitimately off, but if ever we can do that in the future, it would be useful here. 148 * Until then, this function will simply return true of the screen is off. 149 */ 150 public boolean isScreenReallyOff() { 151 return !mPowerManager.isScreenOn(); 152 } 153 154 /** 155 * Updates the wake lock used to control proximity sensor behavior, 156 * based on the current state of the phone. 157 * 158 * On devices that have a proximity sensor, to avoid false touches 159 * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock 160 * whenever the phone is off hook. (When held, that wake lock causes 161 * the screen to turn off automatically when the sensor detects an 162 * object close to the screen.) 163 * 164 * This method is a no-op for devices that don't have a proximity 165 * sensor. 166 * 167 * Proximity wake lock will *not* be held if any one of the 168 * conditions is true while on a call: 169 * 1) If the audio is routed via Bluetooth 170 * 2) If a wired headset is connected 171 * 3) if the speaker is ON 172 * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden) 173 */ 174 private synchronized void updateProximitySensorMode() { 175 final int audioMode = mAudioModeProvider.getAudioMode(); 176 177 // turn proximity sensor off and turn screen on immediately if 178 // we are using a headset, the keyboard is open, or the device 179 // is being held in a horizontal position. 180 boolean screenOnImmediately = (AudioState.ROUTE_WIRED_HEADSET == audioMode 181 || AudioState.ROUTE_SPEAKER == audioMode 182 || AudioState.ROUTE_BLUETOOTH == audioMode 183 || mIsHardKeyboardOpen); 184 185 // We do not keep the screen off when the user is outside in-call screen and we are 186 // horizontal, but we do not force it on when we become horizontal until the 187 // proximity sensor goes negative. 188 final boolean horizontal = 189 (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL); 190 screenOnImmediately |= !mUiShowing && horizontal; 191 192 // We do not keep the screen off when dialpad is visible, we are horizontal, and 193 // the in-call screen is being shown. 194 // At that moment we're pretty sure users want to use it, instead of letting the 195 // proximity sensor turn off the screen by their hands. 196 screenOnImmediately |= mDialpadVisible && horizontal; 197 198 Log.v(this, "screenonImmediately: ", screenOnImmediately); 199 200 Log.i(this, Objects.toStringHelper(this) 201 .add("keybrd", mIsHardKeyboardOpen ? 1 : 0) 202 .add("dpad", mDialpadVisible ? 1 : 0) 203 .add("offhook", mIsPhoneOffhook ? 1 : 0) 204 .add("hor", horizontal ? 1 : 0) 205 .add("ui", mUiShowing ? 1 : 0) 206 .add("aud", AudioState.audioRouteToString(audioMode)) 207 .toString()); 208 209 if (mIsPhoneOffhook && !screenOnImmediately) { 210 Log.d(this, "Turning on proximity sensor"); 211 // Phone is in use! Arrange for the screen to turn off 212 // automatically when the sensor detects a close object. 213 TelecomAdapter.getInstance().turnOnProximitySensor(); 214 } else { 215 Log.d(this, "Turning off proximity sensor"); 216 // Phone is either idle, or ringing. We don't want any special proximity sensor 217 // behavior in either case. 218 TelecomAdapter.getInstance().turnOffProximitySensor(screenOnImmediately); 219 } 220 } 221} 222