From 91037cc532bfdbf991228c2d5ee7901bffb92be4 Mon Sep 17 00:00:00 2001 From: Nancy Chen Date: Tue, 15 Sep 2015 10:14:38 -0700 Subject: 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 --- .../dialer/voicemail/VoicemailAudioManager.java | 140 ++++++++++++++++++++- .../voicemail/VoicemailPlaybackPresenter.java | 34 +++-- .../dialer/voicemail/WiredHeadsetManager.java | 88 +++++++++++++ 3 files changed, 248 insertions(+), 14 deletions(-) create mode 100644 src/com/android/dialer/voicemail/WiredHeadsetManager.java (limited to 'src/com/android/dialer/voicemail') 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); @@ -312,10 +313,19 @@ 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 -- cgit v1.2.3