summaryrefslogtreecommitdiff
path: root/src/com/android/dialer/voicemail
diff options
context:
space:
mode:
authorNancy Chen <nancychen@google.com>2015-09-15 10:14:38 -0700
committerNancy Chen <nancychen@google.com>2015-09-17 18:33:51 -0700
commit91037cc532bfdbf991228c2d5ee7901bffb92be4 (patch)
tree355dd6aacecf8f1ba19cf37349cc9175fb6da2f2 /src/com/android/dialer/voicemail
parent5a1f189507603f7515b04fb798dd5d4d246a61a1 (diff)
Handle wired headset during voicemail playback.
- Switch to wired headset when plugged in even if playing on speaker - Remember if wired headset overrode speaker so it can be turned back on when the headset is unplugged. - Ensure proximity sensor is turned on iff the earpiece is the audio source. Bug: 23816959 Change-Id: I952c24ee51139f21a17344acd7698c9ed8f52860
Diffstat (limited to 'src/com/android/dialer/voicemail')
-rw-r--r--src/com/android/dialer/voicemail/VoicemailAudioManager.java140
-rw-r--r--src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java34
-rw-r--r--src/com/android/dialer/voicemail/WiredHeadsetManager.java88
3 files changed, 248 insertions, 14 deletions
diff --git a/src/com/android/dialer/voicemail/VoicemailAudioManager.java b/src/com/android/dialer/voicemail/VoicemailAudioManager.java
index e64e180b6..267eeca09 100644
--- a/src/com/android/dialer/voicemail/VoicemailAudioManager.java
+++ b/src/com/android/dialer/voicemail/VoicemailAudioManager.java
@@ -19,25 +19,36 @@ package com.android.dialer.voicemail;
import android.content.Context;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.telecom.CallAudioState;
import android.util.Log;
+import java.util.Objects;
import java.util.concurrent.RejectedExecutionException;
/**
* This class manages all audio changes for voicemail playback.
*/
-final class VoicemailAudioManager implements OnAudioFocusChangeListener {
+final class VoicemailAudioManager implements OnAudioFocusChangeListener,
+ WiredHeadsetManager.Listener {
private static final String TAG = VoicemailAudioManager.class.getSimpleName();
public static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL;
private AudioManager mAudioManager;
private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
+ private WiredHeadsetManager mWiredHeadsetManager;
+ private boolean mWasSpeakerOn;
+ private CallAudioState mCallAudioState;
public VoicemailAudioManager(Context context,
VoicemailPlaybackPresenter voicemailPlaybackPresenter) {
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mVoicemailPlaybackPresenter = voicemailPlaybackPresenter;
+ mWiredHeadsetManager = new WiredHeadsetManager(context);
+ mWiredHeadsetManager.setListener(this);
+
+ mCallAudioState = getInitialAudioState();
+ Log.i(TAG, "Initial audioState = " + mCallAudioState);
}
public void requestAudioFocus() {
@@ -60,14 +71,131 @@ final class VoicemailAudioManager implements OnAudioFocusChangeListener {
mVoicemailPlaybackPresenter.onAudioFocusChange(focusChange == AudioManager.AUDIOFOCUS_GAIN);
}
- public void turnOnSpeaker(boolean on) {
+ @Override
+ public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
+ Log.i(TAG, "wired headset was plugged in changed: " + oldIsPluggedIn
+ + " -> "+ newIsPluggedIn);
+
+ if (oldIsPluggedIn == newIsPluggedIn) {
+ return;
+ }
+
+ int newRoute = mCallAudioState.getRoute(); // start out with existing route
+ if (newIsPluggedIn) {
+ newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
+ } else {
+ if (mWasSpeakerOn) {
+ newRoute = CallAudioState.ROUTE_SPEAKER;
+ } else {
+ newRoute = CallAudioState.ROUTE_EARPIECE;
+ }
+ }
+
+ mVoicemailPlaybackPresenter.setSpeakerphoneOn(newRoute == CallAudioState.ROUTE_SPEAKER);
+
+ // We need to call this every time even if we do not change the route because the supported
+ // routes changed either to include or not include WIRED_HEADSET.
+ setSystemAudioState(
+ new CallAudioState(false /* muted */, newRoute, calculateSupportedRoutes()));
+ }
+
+ public void setSpeakerphoneOn(boolean on) {
+ setAudioRoute(on ? CallAudioState.ROUTE_SPEAKER : CallAudioState.ROUTE_WIRED_OR_EARPIECE);
+ }
+
+ public boolean isWiredHeadsetPluggedIn() {
+ return mWiredHeadsetManager.isPluggedIn();
+ }
+
+ public void registerReceivers() {
+ // Receivers is plural because we expect to add bluetooth support.
+ mWiredHeadsetManager.registerReceiver();
+ }
+
+ public void unregisterReceivers() {
+ mWiredHeadsetManager.unregisterReceiver();
+ }
+
+ /**
+ * Change the audio route, for example from earpiece to speakerphone.
+ *
+ * @param route The new audio route to use. See {@link CallAudioState}.
+ */
+ void setAudioRoute(int route) {
+ Log.v(TAG, "setAudioRoute, route: " + CallAudioState.audioRouteToString(route));
+
+ // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
+ int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask());
+
+ // If route is unsupported, do nothing.
+ if ((mCallAudioState.getSupportedRouteMask() | newRoute) == 0) {
+ Log.w(TAG, "Asking to set to a route that is unsupported: " + newRoute);
+ return;
+ }
+
+ if (mCallAudioState.getRoute() != newRoute) {
+ // Remember the new speaker state so it can be restored when the user plugs and unplugs
+ // a headset.
+ mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER;
+ setSystemAudioState(new CallAudioState(false /* muted */, newRoute,
+ mCallAudioState.getSupportedRouteMask()));
+ }
+ }
+
+ private CallAudioState getInitialAudioState() {
+ int supportedRouteMask = calculateSupportedRoutes();
+ int route = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE,
+ supportedRouteMask);
+ return new CallAudioState(false /* muted */, route, supportedRouteMask);
+ }
+
+ private int calculateSupportedRoutes() {
+ int routeMask = CallAudioState.ROUTE_SPEAKER;
+ if (mWiredHeadsetManager.isPluggedIn()) {
+ routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
+ } else {
+ routeMask |= CallAudioState.ROUTE_EARPIECE;
+ }
+ return routeMask;
+ }
+
+ private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
+ // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
+ // ROUTE_WIRED_OR_EARPIECE so that callers don't have to make a call to check which is
+ // supported before calling setAudioRoute.
+ if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) {
+ route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
+ if (route == 0) {
+ Log.wtf(TAG, "One of wired headset or earpiece should always be valid.");
+ // assume earpiece in this case.
+ route = CallAudioState.ROUTE_EARPIECE;
+ }
+ }
+ return route;
+ }
+
+ private void setSystemAudioState(CallAudioState callAudioState) {
+ CallAudioState oldAudioState = mCallAudioState;
+ mCallAudioState = callAudioState;
+
+ Log.i(TAG, "setSystemAudioState: changing from " + oldAudioState + " to "
+ + mCallAudioState);
+
+ // Audio route.
+ if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
+ turnOnSpeaker(true);
+ } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE ||
+ mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) {
+ // Just handle turning off the speaker, the system will handle switching between wired
+ // headset and earpiece.
+ turnOnSpeaker(false);
+ }
+ }
+
+ private void turnOnSpeaker(boolean on) {
if (mAudioManager.isSpeakerphoneOn() != on) {
Log.i(TAG, "turning speaker phone on: " + on);
mAudioManager.setSpeakerphoneOn(on);
}
}
-
- public boolean isSpeakerphoneOn() {
- return mAudioManager.isSpeakerphoneOn();
- }
} \ No newline at end of file
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index 7a2bffcd9..8b8b7c539 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -253,6 +253,7 @@ public class VoicemailPlaybackPresenter implements MediaPlayer.OnPreparedListene
mPosition = 0;
// Default to earpiece.
setSpeakerphoneOn(false);
+ mVoicemailAudioManager.setSpeakerphoneOn(false);
} else {
// Update the view to the current speakerphone state.
mView.onSpeakerphoneOn(mIsSpeakerphoneOn);
@@ -313,9 +314,18 @@ public class VoicemailPlaybackPresenter implements MediaPlayer.OnPreparedListene
}
/**
+ * Must be invoked when the parent activity is resumed.
+ */
+ public void onResume() {
+ mVoicemailAudioManager.registerReceivers();
+ }
+
+ /**
* Must be invoked when the parent activity is paused.
*/
public void onPause() {
+ mVoicemailAudioManager.unregisterReceivers();
+
if (mContext != null && mIsPrepared
&& mInitialOrientation != mContext.getResources().getConfiguration().orientation) {
// If an orientation change triggers the pause, retain the MediaPlayer.
@@ -329,6 +339,7 @@ public class VoicemailPlaybackPresenter implements MediaPlayer.OnPreparedListene
if (mActivity != null) {
mActivity.getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
}
+
}
/**
@@ -637,9 +648,8 @@ public class VoicemailPlaybackPresenter implements MediaPlayer.OnPreparedListene
// Grab audio focus.
// Can throw RejectedExecutionException.
mVoicemailAudioManager.requestAudioFocus();
-
- setSpeakerphoneOn(mIsSpeakerphoneOn);
mMediaPlayer.start();
+ setSpeakerphoneOn(mIsSpeakerphoneOn);
} catch (RejectedExecutionException e) {
handleError(e);
}
@@ -725,21 +735,29 @@ public class VoicemailPlaybackPresenter implements MediaPlayer.OnPreparedListene
}
}
+ /**
+ * This is for use by UI interactions only. It simplifies UI logic.
+ */
public void toggleSpeakerphone() {
+ mVoicemailAudioManager.setSpeakerphoneOn(!mIsSpeakerphoneOn);
setSpeakerphoneOn(!mIsSpeakerphoneOn);
}
- private void setSpeakerphoneOn(boolean on) {
+ /**
+ * This method only handles app-level changes to the speakerphone. Audio layer changes should
+ * be handled separately. This is so that the VoicemailAudioManager can trigger changes to
+ * the presenter without the presenter triggering the audio manager and duplicating actions.
+ */
+ public void setSpeakerphoneOn(boolean on) {
mView.onSpeakerphoneOn(on);
- if (mIsSpeakerphoneOn == on) {
- return;
- }
mIsSpeakerphoneOn = on;
- mVoicemailAudioManager.turnOnSpeaker(on);
+ // This should run even if speakerphone is not being toggled because we may be switching
+ // from earpiece to headphone and vise versa. Also upon initial setup the default audio
+ // source is the earpiece, so we want to trigger the proximity sensor.
if (mIsPlaying) {
- if (on) {
+ if (on || mVoicemailAudioManager.isWiredHeadsetPluggedIn()) {
disableProximitySensor(false /* waitForFarState */);
if (mIsPrepared && mMediaPlayer != null && mMediaPlayer.isPlaying()) {
mActivity.getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
diff --git a/src/com/android/dialer/voicemail/WiredHeadsetManager.java b/src/com/android/dialer/voicemail/WiredHeadsetManager.java
new file mode 100644
index 000000000..7351f4f01
--- /dev/null
+++ b/src/com/android/dialer/voicemail/WiredHeadsetManager.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2015 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.dialer.voicemail;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.util.Log;
+
+/** Listens for and caches headset state. */
+class WiredHeadsetManager {
+ private static final String TAG = WiredHeadsetManager.class.getSimpleName();
+
+ interface Listener {
+ void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn);
+ }
+
+ /** Receiver for wired headset plugged and unplugged events. */
+ private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (AudioManager.ACTION_HEADSET_PLUG.equals(intent.getAction())) {
+ boolean isPluggedIn = intent.getIntExtra("state", 0) == 1;
+ Log.v(TAG, "ACTION_HEADSET_PLUG event, plugged in: " + isPluggedIn);
+ onHeadsetPluggedInChanged(isPluggedIn);
+ }
+ }
+ }
+
+ private final WiredHeadsetBroadcastReceiver mReceiver;
+ private boolean mIsPluggedIn;
+ private Listener mListener;
+ private Context mContext;
+
+ WiredHeadsetManager(Context context) {
+ mContext = context;
+ mReceiver = new WiredHeadsetBroadcastReceiver();
+
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mIsPluggedIn = audioManager.isWiredHeadsetOn();
+
+ }
+
+ void setListener(Listener listener) {
+ mListener = listener;
+ }
+
+ boolean isPluggedIn() {
+ return mIsPluggedIn;
+ }
+
+ void registerReceiver() {
+ // Register for misc other intent broadcasts.
+ IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
+ mContext.registerReceiver(mReceiver, intentFilter);
+ }
+
+ void unregisterReceiver() {
+ mContext.unregisterReceiver(mReceiver);
+ }
+
+ private void onHeadsetPluggedInChanged(boolean isPluggedIn) {
+ if (mIsPluggedIn != isPluggedIn) {
+ Log.v(TAG, "onHeadsetPluggedInChanged, mIsPluggedIn: " + mIsPluggedIn + " -> "
+ + isPluggedIn);
+ boolean oldIsPluggedIn = mIsPluggedIn;
+ mIsPluggedIn = isPluggedIn;
+ if (mListener != null) {
+ mListener.onWiredHeadsetPluggedInChanged(oldIsPluggedIn, mIsPluggedIn);
+ }
+ }
+ }
+} \ No newline at end of file