blob: fe6cf5f45d1e2b9e91d28b6947aa2ad49bac3dd5 [file] [log] [blame]
/*
* 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.Context;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.telecom.CallAudioState;
import android.util.Log;
import java.util.concurrent.RejectedExecutionException;
/**
* This class manages all audio changes for voicemail playback.
*/
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() {
int result = mAudioManager.requestAudioFocus(
this,
PLAYBACK_STREAM,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
throw new RejectedExecutionException("Could not capture audio focus.");
}
}
public void abandonAudioFocus() {
mAudioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
Log.d(TAG, "onAudioFocusChange: focusChange=" + focusChange);
mVoicemailPlaybackPresenter.onAudioFocusChange(focusChange == AudioManager.AUDIOFOCUS_GAIN);
}
@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);
}
}
}