summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--InCallUI/AndroidManifest.xml1
-rw-r--r--InCallUI/src/com/android/incallui/AccelerometerListener.java161
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonPresenter.java6
-rw-r--r--InCallUI/src/com/android/incallui/InCallActivity.java11
-rw-r--r--InCallUI/src/com/android/incallui/InCallPresenter.java13
-rw-r--r--InCallUI/src/com/android/incallui/ProximitySensor.java232
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.");
+ }
+ }
+ }
+ }
+ }
+
+}