diff options
Diffstat (limited to 'java/com/android/incallui/answerproximitysensor')
5 files changed, 428 insertions, 0 deletions
diff --git a/java/com/android/incallui/answerproximitysensor/AnswerProximitySensor.java b/java/com/android/incallui/answerproximitysensor/AnswerProximitySensor.java new file mode 100644 index 000000000..edc3db34b --- /dev/null +++ b/java/com/android/incallui/answerproximitysensor/AnswerProximitySensor.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2016 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.answerproximitysensor; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.PowerManager; +import android.view.Display; +import com.android.dialer.common.ConfigProviderBindings; +import com.android.dialer.common.LogUtil; +import com.android.incallui.call.DialerCall; +import com.android.incallui.call.DialerCall.SessionModificationState; +import com.android.incallui.call.DialerCall.State; +import com.android.incallui.call.DialerCallListener; + +/** + * This class prevents users from accidentally answering calls by keeping the screen off until the + * proximity sensor is unblocked. If the screen is already on or if this is a call waiting call then + * nothing is done. + */ +public class AnswerProximitySensor + implements DialerCallListener, AnswerProximityWakeLock.ScreenOnListener { + + private static final String CONFIG_ANSWER_PROXIMITY_SENSOR_ENABLED = + "answer_proximity_sensor_enabled"; + private static final String CONFIG_ANSWER_PSEUDO_PROXIMITY_WAKE_LOCK_ENABLED = + "answer_pseudo_proximity_wake_lock_enabled"; + + private final DialerCall call; + private final AnswerProximityWakeLock answerProximityWakeLock; + + public static boolean shouldUse(Context context, DialerCall call) { + // Don't use the AnswerProximitySensor for call waiting and other states. Those states are + // handled by the general ProximitySensor code. + if (call.getState() != State.INCOMING) { + LogUtil.i("AnswerProximitySensor.shouldUse", "call state is not incoming"); + return false; + } + + if (!ConfigProviderBindings.get(context) + .getBoolean(CONFIG_ANSWER_PROXIMITY_SENSOR_ENABLED, true)) { + LogUtil.i("AnswerProximitySensor.shouldUse", "disabled by config"); + return false; + } + + if (!context + .getSystemService(PowerManager.class) + .isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { + LogUtil.i("AnswerProximitySensor.shouldUse", "wake lock level not supported"); + return false; + } + + if (isDefaultDisplayOn(context)) { + LogUtil.i("AnswerProximitySensor.shouldUse", "display is already on"); + return false; + } + + return true; + } + + public AnswerProximitySensor( + Context context, DialerCall call, PseudoScreenState pseudoScreenState) { + this.call = call; + + LogUtil.i("AnswerProximitySensor.constructor", "acquiring lock"); + if (ConfigProviderBindings.get(context) + .getBoolean(CONFIG_ANSWER_PSEUDO_PROXIMITY_WAKE_LOCK_ENABLED, true)) { + answerProximityWakeLock = new PseudoProximityWakeLock(context, pseudoScreenState); + } else { + // TODO: choose a wake lock implementation base on framework/device. + // These bugs requires the PseudoProximityWakeLock workaround: + // b/30439151 Proximity sensor not working on M + // b/31499931 fautly touch input when screen is off on marlin/sailfish + answerProximityWakeLock = new SystemProximityWakeLock(context); + } + answerProximityWakeLock.setScreenOnListener(this); + answerProximityWakeLock.acquire(); + + call.addListener(this); + } + + private void cleanup() { + call.removeListener(this); + releaseProximityWakeLock(); + } + + private void releaseProximityWakeLock() { + if (answerProximityWakeLock.isHeld()) { + LogUtil.i("AnswerProximitySensor.releaseProximityWakeLock", "releasing lock"); + answerProximityWakeLock.release(); + } + } + + private static boolean isDefaultDisplayOn(Context context) { + Display display = + context.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY); + return display.getState() == Display.STATE_ON; + } + + @Override + public void onDialerCallDisconnect() { + LogUtil.i("AnswerProximitySensor.onDialerCallDisconnect", null); + cleanup(); + } + + @Override + public void onDialerCallUpdate() { + if (call.getState() != State.INCOMING) { + LogUtil.i("AnswerProximitySensor.onDialerCallUpdate", "no longer incoming, cleaning up"); + cleanup(); + } + } + + @Override + public void onDialerCallChildNumberChange() {} + + @Override + public void onDialerCallLastForwardedNumberChange() {} + + @Override + public void onDialerCallUpgradeToVideo() {} + + @Override + public void onWiFiToLteHandover() {} + + @Override + public void onHandoverToWifiFailure() {} + + @Override + public void onDialerCallSessionModificationStateChange(@SessionModificationState int state) {} + + @Override + public void onScreenOn() { + cleanup(); + } +} diff --git a/java/com/android/incallui/answerproximitysensor/AnswerProximityWakeLock.java b/java/com/android/incallui/answerproximitysensor/AnswerProximityWakeLock.java new file mode 100644 index 000000000..94abe9c85 --- /dev/null +++ b/java/com/android/incallui/answerproximitysensor/AnswerProximityWakeLock.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 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.answerproximitysensor; + +/** + * Interface to wrap around the {@link android.os.PowerManager.WakeLock} for custom implementations. + */ +public interface AnswerProximityWakeLock { + + /** Called when the wake lock turned the screen back on. */ + interface ScreenOnListener { + + void onScreenOn(); + } + + void acquire(); + + void release(); + + boolean isHeld(); + + void setScreenOnListener(ScreenOnListener listener); +} diff --git a/java/com/android/incallui/answerproximitysensor/PseudoProximityWakeLock.java b/java/com/android/incallui/answerproximitysensor/PseudoProximityWakeLock.java new file mode 100644 index 000000000..c7844d47d --- /dev/null +++ b/java/com/android/incallui/answerproximitysensor/PseudoProximityWakeLock.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 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.answerproximitysensor; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.support.annotation.Nullable; +import com.android.dialer.common.LogUtil; + +/** + * A fake PROXIMITY_SCREEN_OFF_WAKE_LOCK implemented by the app. It will use {@link + * PseudoScreenState} to fake a black screen when the proximity sensor is near. + */ +public class PseudoProximityWakeLock implements AnswerProximityWakeLock, SensorEventListener { + + private final Context context; + private final PseudoScreenState pseudoScreenState; + private final Sensor proximitySensor; + + @Nullable private ScreenOnListener listener; + private boolean isHeld; + + public PseudoProximityWakeLock(Context context, PseudoScreenState pseudoScreenState) { + this.context = context; + this.pseudoScreenState = pseudoScreenState; + pseudoScreenState.setOn(true); + proximitySensor = + context.getSystemService(SensorManager.class).getDefaultSensor(Sensor.TYPE_PROXIMITY); + } + + @Override + public void acquire() { + isHeld = true; + context + .getSystemService(SensorManager.class) + .registerListener(this, proximitySensor, SensorManager.SENSOR_DELAY_NORMAL); + } + + @Override + public void release() { + isHeld = false; + context.getSystemService(SensorManager.class).unregisterListener(this); + pseudoScreenState.setOn(true); + } + + @Override + public boolean isHeld() { + return isHeld; + } + + @Override + public void setScreenOnListener(ScreenOnListener listener) { + this.listener = listener; + } + + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + boolean near = sensorEvent.values[0] < sensorEvent.sensor.getMaximumRange(); + LogUtil.i("AnswerProximitySensor.PseudoProximityWakeLock.onSensorChanged", "near: " + near); + pseudoScreenState.setOn(!near); + if (!near && listener != null) { + listener.onScreenOn(); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) {} +} diff --git a/java/com/android/incallui/answerproximitysensor/PseudoScreenState.java b/java/com/android/incallui/answerproximitysensor/PseudoScreenState.java new file mode 100644 index 000000000..eda0ee720 --- /dev/null +++ b/java/com/android/incallui/answerproximitysensor/PseudoScreenState.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 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.answerproximitysensor; + +import android.util.ArraySet; +import java.util.Set; + +/** + * Stores a fake screen on/off state for the {@link InCallActivity}. If InCallActivity see the state + * is off, it will draw a black view over the activity pretending the screen is off. + * + * <p>If the screen is already touched when the screen is turned on, the OS behavior is sending a + * new DOWN event once the point started moving and then behave as a normal gesture. To prevent + * accidental answer/rejects, touches that started when the screen is off should be ignored. + * + * <p>b/31499931 on certain devices with N-DR1, if the screen is already touched when the screen is + * turned on, a "DOWN MOVE UP" will be sent for each movement before the touch is actually released. + * These events is hard to discern from other normal events, and keeping the screen on reduces its' + * probability. + */ +public class PseudoScreenState { + + /** Notifies when the on state has changed. */ + public interface StateChangedListener { + void onPseudoScreenStateChanged(boolean isOn); + } + + private final Set<StateChangedListener> listeners = new ArraySet<>(); + + private boolean on = true; + + public boolean isOn() { + return on; + } + + public void setOn(boolean value) { + if (on != value) { + on = value; + for (StateChangedListener listener : listeners) { + listener.onPseudoScreenStateChanged(on); + } + } + } + + public void addListener(StateChangedListener listener) { + listeners.add(listener); + } + + public void removeListener(StateChangedListener listener) { + listeners.remove(listener); + } +} diff --git a/java/com/android/incallui/answerproximitysensor/SystemProximityWakeLock.java b/java/com/android/incallui/answerproximitysensor/SystemProximityWakeLock.java new file mode 100644 index 000000000..776e9a42d --- /dev/null +++ b/java/com/android/incallui/answerproximitysensor/SystemProximityWakeLock.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 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.answerproximitysensor; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; +import android.os.PowerManager; +import android.support.annotation.Nullable; +import android.view.Display; +import com.android.dialer.common.LogUtil; + +/** The normal PROXIMITY_SCREEN_OFF_WAKE_LOCK provided by the OS. */ +public class SystemProximityWakeLock implements AnswerProximityWakeLock, DisplayListener { + + private static final String TAG = "SystemProximityWakeLock"; + + private final Context context; + private final PowerManager.WakeLock wakeLock; + + @Nullable private ScreenOnListener listener; + + public SystemProximityWakeLock(Context context) { + this.context = context; + wakeLock = + context + .getSystemService(PowerManager.class) + .newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG); + } + + @Override + public void acquire() { + wakeLock.acquire(); + context.getSystemService(DisplayManager.class).registerDisplayListener(this, null); + } + + @Override + public void release() { + wakeLock.release(); + context.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } + + @Override + public boolean isHeld() { + return wakeLock.isHeld(); + } + + @Override + public void setScreenOnListener(ScreenOnListener listener) { + this.listener = listener; + } + + @Override + public void onDisplayAdded(int displayId) {} + + @Override + public void onDisplayRemoved(int displayId) {} + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + if (isDefaultDisplayOn(context)) { + LogUtil.i("SystemProximityWakeLock.onDisplayChanged", "display turned on"); + if (listener != null) { + listener.onScreenOn(); + } + } + } + } + + private static boolean isDefaultDisplayOn(Context context) { + Display display = + context.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY); + return display.getState() != Display.STATE_OFF; + } +} |