diff options
author | Santos Cordon <santoscordon@google.com> | 2013-08-20 15:07:51 -0700 |
---|---|---|
committer | Santos Cordon <santoscordon@google.com> | 2013-08-21 09:30:07 -0700 |
commit | e7a567eea1613627e177c7445549abae0c89a6ee (patch) | |
tree | 3595ac8a4327a3c2271caf4a25a5704d931a6834 | |
parent | 62421ffb6123b5064646af20b7c9d40b76ef0062 (diff) |
Adding proximity sensor code to in-call UI.
Many of these changes are verbatim code copies from what used to exist
in services/Telephony. The rest of them are straight logic copies that
should do the exact same things.
New class ProximitySensor manages the proximity behavior. It receives
device state from InCallPresenter, AudioModeProvider, CallButtonPresenter,
and AcceleromterListener, the last of which is a transplanted class from
services/Telephony.
ProximitySensor listens for the following events:
1. Change in the call state
2. Change in the audio mode
3. Change in the device orientation (from AccelerometerListener)
4. Change in the dialpad visibility
5. Change in hard keyboard open/close events.
6. Change in foreground position of InCall UI app.
It uses these to figure out when to enable/disable proximity sensor.
CL that removes code from TeleService: I77e0d15ad1a8f5a090c1368db98edaa246dbcd72
bug: 10366512
Change-Id: I5c2ea6daa9443e7ad77c67f272bc0bafdb060e5e
-rw-r--r-- | InCallUI/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | InCallUI/src/com/android/incallui/AccelerometerListener.java | 161 | ||||
-rw-r--r-- | InCallUI/src/com/android/incallui/CallButtonPresenter.java | 6 | ||||
-rw-r--r-- | InCallUI/src/com/android/incallui/InCallActivity.java | 11 | ||||
-rw-r--r-- | InCallUI/src/com/android/incallui/InCallPresenter.java | 13 | ||||
-rw-r--r-- | InCallUI/src/com/android/incallui/ProximitySensor.java | 232 |
6 files changed, 424 insertions, 0 deletions
diff --git a/InCallUI/AndroidManifest.xml b/InCallUI/AndroidManifest.xml index 3196dba60..42d8e3173 100644 --- a/InCallUI/AndroidManifest.xml +++ b/InCallUI/AndroidManifest.xml @@ -23,6 +23,7 @@ <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.WAKE_LOCK"/> <application android:name="InCallApp" diff --git a/InCallUI/src/com/android/incallui/AccelerometerListener.java b/InCallUI/src/com/android/incallui/AccelerometerListener.java new file mode 100644 index 000000000..1a7077866 --- /dev/null +++ b/InCallUI/src/com/android/incallui/AccelerometerListener.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.incallui; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +/** + * This class is used to listen to the accelerometer to monitor the + * orientation of the phone. The client of this class is notified when + * the orientation changes between horizontal and vertical. + */ +public final class AccelerometerListener { + private static final String TAG = "AccelerometerListener"; + private static final boolean DEBUG = true; + private static final boolean VDEBUG = false; + + private SensorManager mSensorManager; + private Sensor mSensor; + + // mOrientation is the orientation value most recently reported to the client. + private int mOrientation; + + // mPendingOrientation is the latest orientation computed based on the sensor value. + // This is sent to the client after a rebounce delay, at which point it is copied to + // mOrientation. + private int mPendingOrientation; + + private OrientationListener mListener; + + // Device orientation + public static final int ORIENTATION_UNKNOWN = 0; + public static final int ORIENTATION_VERTICAL = 1; + public static final int ORIENTATION_HORIZONTAL = 2; + + private static final int ORIENTATION_CHANGED = 1234; + + private static final int VERTICAL_DEBOUNCE = 100; + private static final int HORIZONTAL_DEBOUNCE = 500; + private static final double VERTICAL_ANGLE = 50.0; + + public interface OrientationListener { + public void orientationChanged(int orientation); + } + + public AccelerometerListener(Context context, OrientationListener listener) { + mListener = listener; + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + } + + public void enable(boolean enable) { + if (DEBUG) Log.d(TAG, "enable(" + enable + ")"); + synchronized (this) { + if (enable) { + mOrientation = ORIENTATION_UNKNOWN; + mPendingOrientation = ORIENTATION_UNKNOWN; + mSensorManager.registerListener(mSensorListener, mSensor, + SensorManager.SENSOR_DELAY_NORMAL); + } else { + mSensorManager.unregisterListener(mSensorListener); + mHandler.removeMessages(ORIENTATION_CHANGED); + } + } + } + + private void setOrientation(int orientation) { + synchronized (this) { + if (mPendingOrientation == orientation) { + // Pending orientation has not changed, so do nothing. + return; + } + + // Cancel any pending messages. + // We will either start a new timer or cancel alltogether + // if the orientation has not changed. + mHandler.removeMessages(ORIENTATION_CHANGED); + + if (mOrientation != orientation) { + // Set timer to send an event if the orientation has changed since its + // previously reported value. + mPendingOrientation = orientation; + final Message m = mHandler.obtainMessage(ORIENTATION_CHANGED); + // set delay to our debounce timeout + int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE + : HORIZONTAL_DEBOUNCE); + mHandler.sendMessageDelayed(m, delay); + } else { + // no message is pending + mPendingOrientation = ORIENTATION_UNKNOWN; + } + } + } + + private void onSensorEvent(double x, double y, double z) { + if (VDEBUG) Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")"); + + // If some values are exactly zero, then likely the sensor is not powered up yet. + // ignore these events to avoid false horizontal positives. + if (x == 0.0 || y == 0.0 || z == 0.0) return; + + // magnitude of the acceleration vector projected onto XY plane + final double xy = Math.sqrt(x*x + y*y); + // compute the vertical angle + double angle = Math.atan2(xy, z); + // convert to degrees + angle = angle * 180.0 / Math.PI; + final int orientation = (angle > VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL); + if (VDEBUG) Log.d(TAG, "angle: " + angle + " orientation: " + orientation); + setOrientation(orientation); + } + + SensorEventListener mSensorListener = new SensorEventListener() { + public void onSensorChanged(SensorEvent event) { + onSensorEvent(event.values[0], event.values[1], event.values[2]); + } + + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // ignore + } + }; + + Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case ORIENTATION_CHANGED: + synchronized (this) { + mOrientation = mPendingOrientation; + if (DEBUG) { + Log.d(TAG, "orientation: " + + (mOrientation == ORIENTATION_HORIZONTAL ? "horizontal" + : (mOrientation == ORIENTATION_VERTICAL ? "vertical" + : "unknown"))); + } + mListener.orientationChanged(mOrientation); + } + break; + } + } + }; +} diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java index 12cc65663..233231754 100644 --- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java +++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java @@ -33,6 +33,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto private Call mCall; private AudioModeProvider mAudioModeProvider; + private ProximitySensor mProximitySensor; public CallButtonPresenter() { } @@ -166,6 +167,7 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto public void showDialpadClicked(boolean checked) { Logger.v(this, "Show dialpad " + String.valueOf(checked)); getUi().displayDialpad(checked); + mProximitySensor.onDialpadVisible(checked); } private void updateUi(InCallState state, Call call) { @@ -204,6 +206,10 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto mAudioModeProvider.addListener(this); } + public void setProximitySensor(ProximitySensor proximitySensor) { + mProximitySensor = proximitySensor; + } + public interface CallButtonUi extends Ui { void setVisible(boolean on); void setMute(boolean on); diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java index 5edfaa32a..776f7e013 100644 --- a/InCallUI/src/com/android/incallui/InCallActivity.java +++ b/InCallUI/src/com/android/incallui/InCallActivity.java @@ -19,6 +19,7 @@ package com.android.incallui; import android.app.Activity; import android.app.Fragment; import android.content.Intent; +import android.content.res.Configuration; import android.os.Bundle; import android.view.KeyEvent; import android.view.View; @@ -51,6 +52,9 @@ public class InCallActivity extends Activity { requestWindowFeature(Window.FEATURE_NO_TITLE); + // TODO(klp): Do we need to add this back when prox sensor is not available? + // lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; + // Inflate everything in incall_screen.xml and add it to the screen. setContentView(R.layout.incall_screen); @@ -210,6 +214,11 @@ public class InCallActivity extends Activity { return super.onKeyDown(keyCode, event); } + @Override + public void onConfigurationChanged(Configuration config) { + InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config); + } + private void internalResolveIntent(Intent intent) { final String action = intent.getAction(); @@ -282,6 +291,8 @@ public class InCallActivity extends Activity { mCallButtonFragment.getPresenter().setAudioModeProvider( mainPresenter.getAudioModeProvider()); + mCallButtonFragment.getPresenter().setProximitySensor( + mainPresenter.getProximitySensor()); mCallCardFragment.getPresenter().setAudioModeProvider( mainPresenter.getAudioModeProvider()); mCallCardFragment.getPresenter().setContactInfoCache( diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java index 3b400ef7b..1931c1b53 100644 --- a/InCallUI/src/com/android/incallui/InCallPresenter.java +++ b/InCallUI/src/com/android/incallui/InCallPresenter.java @@ -49,6 +49,7 @@ public class InCallPresenter implements CallList.Listener { private InCallActivity mInCallActivity; private boolean mServiceConnected = false; private InCallState mInCallState = InCallState.HIDDEN; + private ProximitySensor mProximitySensor; public static synchronized InCallPresenter getInstance() { if (sInCallPresenter == null) { @@ -74,6 +75,9 @@ public class InCallPresenter implements CallList.Listener { // This only gets called by the service so this is okay. mServiceConnected = true; + mProximitySensor = new ProximitySensor(context, mAudioModeProvider); + addListener(mProximitySensor); + Logger.d(this, "Finished InCallPresenter.setUp"); } @@ -169,6 +173,10 @@ public class InCallPresenter implements CallList.Listener { return mContactInfoCache; } + public ProximitySensor getProximitySensor() { + return mProximitySensor; + } + /** * Hangs up any active or outgoing calls. */ @@ -200,6 +208,10 @@ public class InCallPresenter implements CallList.Listener { if (mStatusBarNotifier != null) { mStatusBarNotifier.updateNotification(mInCallState, mCallList); } + + if (mProximitySensor != null) { + mProximitySensor.onInCallShowing(showing); + } } /** @@ -286,6 +298,7 @@ public class InCallPresenter implements CallList.Listener { private void attemptCleanup() { if (mInCallActivity == null && !mServiceConnected) { Logger.d(this, "Start InCallPresenter.CleanUp"); + mProximitySensor = null; mAudioModeProvider = null; removeListener(mStatusBarNotifier); diff --git a/InCallUI/src/com/android/incallui/ProximitySensor.java b/InCallUI/src/com/android/incallui/ProximitySensor.java new file mode 100644 index 000000000..48dc43c2f --- /dev/null +++ b/InCallUI/src/com/android/incallui/ProximitySensor.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui; + +import android.content.Context; +import android.content.res.Configuration; +import android.os.PowerManager; + +import com.android.incallui.AudioModeProvider.AudioModeListener; +import com.android.incallui.InCallPresenter.InCallState; +import com.android.incallui.InCallPresenter.InCallStateListener; +import com.android.services.telephony.common.AudioMode; + +/** + * Class manages the proximity sensor for the in-call UI. + * We enable the proximity sensor while the user in a phone call. The Proximity sensor turns off + * the touchscreen and display when the user is close to the screen to prevent user's cheek from + * causing touch events. + * The class requires special knowledge of the activity and device state to know when the proximity + * sensor should be enabled and disabled. Most of that state is fed into this class through + * public methods. + */ +public class ProximitySensor implements AccelerometerListener.OrientationListener, + InCallStateListener, AudioModeListener { + private static final String TAG = ProximitySensor.class.getSimpleName(); + + private final PowerManager mPowerManager; + private final PowerManager.WakeLock mProximityWakeLock; + private final AudioModeProvider mAudioModeProvider; + private final AccelerometerListener mAccelerometerListener; + private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN; + private boolean mUiShowing = false; + private boolean mIsPhoneOffhook = false; + private boolean mDialpadVisible; + + // True if the keyboard is currently *not* hidden + // Gets updated whenever there is a Configuration change + private boolean mIsHardKeyboardOpen; + + public ProximitySensor(Context context, AudioModeProvider audioModeProvider) { + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + + if (mPowerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { + mProximityWakeLock = mPowerManager.newWakeLock( + PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); + } else { + mProximityWakeLock = null; + } + Logger.d(this, "onCreate: mProximityWakeLock: ", mProximityWakeLock); + + mAccelerometerListener = new AccelerometerListener(context, this); + mAudioModeProvider = audioModeProvider; + mAudioModeProvider.addListener(this); + } + + /** + * Called to identify when the device is laid down flat. + */ + @Override + public void orientationChanged(int orientation) { + mOrientation = orientation; + updateProximitySensorMode(); + } + + /** + * Called to keep track of the overall UI state. + */ + @Override + public void onStateChange(InCallState state, CallList callList) { + // We ignore incoming state because we do not want to enable proximity + // sensor during incoming call screen + mIsPhoneOffhook = (InCallState.INCALL == state + || InCallState.OUTGOING == state); + + mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN; + mAccelerometerListener.enable(mIsPhoneOffhook); + + updateProximitySensorMode(); + } + + @Override + public void onSupportedAudioMode(int modeMask) { + } + + /** + * Called when the audio mode changes during a call. + */ + @Override + public void onAudioMode(int mode) { + updateProximitySensorMode(); + } + + public void onDialpadVisible(boolean visible) { + mDialpadVisible = visible; + updateProximitySensorMode(); + } + + /** + * Called by InCallActivity to listen for hard keyboard events. + */ + public void onConfigurationChanged(Configuration newConfig) { + mIsHardKeyboardOpen = newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; + + // Update the Proximity sensor based on keyboard state + updateProximitySensorMode(); + } + + /** + * Used to save when the UI goes in and out of the foreground. + */ + public void onInCallShowing(boolean showing) { + if (showing) { + mUiShowing = true; + + // We only consider the UI not showing for instances where another app took the foreground. + // If we stopped showing because the screen is off, we still consider that showing. + } else if (mPowerManager.isScreenOn()) { + mUiShowing = false; + } + updateProximitySensorMode(); + } + + /** + * @return true if this device supports the "proximity sensor + * auto-lock" feature while in-call (see updateProximitySensorMode()). + */ + private boolean proximitySensorModeEnabled() { + // TODO(klp): Do we disable notification's expanded view when app is in foreground and + // proximity sensor is on? Is it even possible to do this any more? + return (mProximityWakeLock != null); + } + + /** + * Updates the wake lock used to control proximity sensor behavior, + * based on the current state of the phone. + * + * On devices that have a proximity sensor, to avoid false touches + * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock + * whenever the phone is off hook. (When held, that wake lock causes + * the screen to turn off automatically when the sensor detects an + * object close to the screen.) + * + * This method is a no-op for devices that don't have a proximity + * sensor. + * + * Proximity wake lock will *not* be held if any one of the + * conditions is true while on a call: + * 1) If the audio is routed via Bluetooth + * 2) If a wired headset is connected + * 3) if the speaker is ON + * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden) + */ + private void updateProximitySensorMode() { + Logger.v(this, "updateProximitySensorMode"); + + if (proximitySensorModeEnabled()) { + Logger.v(this, "keyboard open: ", mIsHardKeyboardOpen); + Logger.v(this, "dialpad visible: ", mDialpadVisible); + Logger.v(this, "isOffhook: ", mIsPhoneOffhook); + + synchronized (mProximityWakeLock) { + + final int audioMode = mAudioModeProvider.getAudioMode(); + Logger.v(this, "audioMode: ", AudioMode.toString(audioMode)); + + // turn proximity sensor off and turn screen on immediately if + // we are using a headset, the keyboard is open, or the device + // is being held in a horizontal position. + boolean screenOnImmediately = (AudioMode.WIRED_HEADSET == audioMode + || AudioMode.SPEAKER == audioMode + || AudioMode.BLUETOOTH == audioMode + || mIsHardKeyboardOpen); + + // We do not keep the screen off when the user is outside in-call screen and we are + // horizontal, but we do not force it on when we become horizontal until the + // proximity sensor goes negative. + final boolean horizontal = + (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL); + Logger.v(this, "horizontal: ", horizontal); + screenOnImmediately |= !mUiShowing && horizontal; + + // We do not keep the screen off when dialpad is visible, we are horizontal, and + // the in-call screen is being shown. + // At that moment we're pretty sure users want to use it, instead of letting the + // proximity sensor turn off the screen by their hands. + screenOnImmediately |= mDialpadVisible && horizontal; + + Logger.v(this, "screenonImmediately: ", screenOnImmediately); + + if (mIsPhoneOffhook && !screenOnImmediately) { + // Phone is in use! Arrange for the screen to turn off + // automatically when the sensor detects a close object. + if (!mProximityWakeLock.isHeld()) { + Logger.d(this, "updateProximitySensorMode: acquiring..."); + mProximityWakeLock.acquire(); + } else { + Logger.v(this, "updateProximitySensorMode: lock already held."); + } + } else { + // Phone is either idle, or ringing. We don't want any + // special proximity sensor behavior in either case. + if (mProximityWakeLock.isHeld()) { + Logger.d(this, "updateProximitySensorMode: releasing..."); + // Wait until user has moved the phone away from his head if we are + // releasing due to the phone call ending. + // Qtherwise, turn screen on immediately + int flags = + (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE); + mProximityWakeLock.release(flags); + } else { + Logger.v(this, "updateProximitySensorMode: lock already released."); + } + } + } + } + } + +} |