Audio focus and ring changes (again).
- Went back to using ringtone. Ringtone class has fallback logic that is
necessary to keep (without duplicating).
- Moved the ringtone player code into AsyncRingtonePlayer and kept the
CallsManager listener code inside ringer into CallAudioManager.java
- Consolidated AudioFocus acquisition into CallAudioManager.
bug: 13365906
Change-Id: I8d7b6a999f594b8f81497aa3f5b7ac5916fdd18e
diff --git a/src/com/android/telecomm/CallAudioManager.java b/src/com/android/telecomm/CallAudioManager.java
index 07d5dbe..0d58f86 100644
--- a/src/com/android/telecomm/CallAudioManager.java
+++ b/src/com/android/telecomm/CallAudioManager.java
@@ -20,40 +20,174 @@
import android.media.AudioManager;
import android.telecomm.CallState;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
/**
* This class manages audio modes, streams and other properties.
*/
final class CallAudioManager extends CallsManagerListenerBase {
+ private AsyncRingtonePlayer mRinger = new AsyncRingtonePlayer();
+
+ private boolean mHasAudioFocus = false;
+
+ /**
+ * Used to keep ordering of unanswered incoming calls. The existence of multiple call services
+ * means that there can easily exist multiple incoming calls and explicit ordering is useful for
+ * maintaining the proper state of the ringer.
+ */
+ private final List<String> mUnansweredCallIds = Lists.newLinkedList();
+
+ /**
+ * Denotes when the ringer is disabled. This is useful in temporarily disabling the ringer when
+ * the a call is answered/rejected by the user, but the call hasn't actually moved out of the
+ * ringing state.
+ */
+ private boolean mIsRingingDisabled = false;
+
+ @Override
+ public void onCallAdded(Call call) {
+ if (call.getState() == CallState.RINGING) {
+ mUnansweredCallIds.add(call.getId());
+ }
+ updateAudio();
+ }
+
+ @Override
+ public void onCallRemoved(Call call) {
+ removeFromUnansweredCallIds(call.getId());
+ updateAudio();
+ }
+
@Override
public void onCallStateChanged(Call call, CallState oldState, CallState newState) {
- switch (newState) {
- case ACTIVE:
- updateAudioFocusForActiveCall(call);
- break;
- case DISCONNECTED:
- updateAudioFocusForNoCall();
- break;
- default:
- break;
+ if (oldState == CallState.RINGING) {
+ removeFromUnansweredCallIds(call.getId());
+ }
+
+ updateAudio();
+ }
+
+ @Override
+ public void onIncomingCallAnswered(Call call) {
+ mIsRingingDisabled = true;
+ updateAudio();
+ }
+
+ @Override
+ public void onIncomingCallRejected(Call call) {
+ mIsRingingDisabled = true;
+ updateAudio();
+ }
+
+ /**
+ * Reads the current state of all calls from CallsManager and sets the appropriate audio modes
+ * as well as triggers the start/stop of the ringer.
+ */
+ private void updateAudio() {
+ CallsManager callsManager = CallsManager.getInstance();
+
+ boolean hasRingingCall = !mIsRingingDisabled && !mUnansweredCallIds.isEmpty();
+ boolean hasLiveCall = callsManager.hasCallWithState(CallState.ACTIVE, CallState.DIALING);
+
+ int mode = hasRingingCall ? AudioManager.MODE_RINGTONE :
+ hasLiveCall ? AudioManager.MODE_IN_CALL :
+ AudioManager.MODE_NORMAL;
+
+ boolean needsFocus = (mode != AudioManager.MODE_NORMAL);
+
+ // Acquiring focus needs to be first, unlike releasing focus, which happens at the end.
+ if (needsFocus) {
+ acquireFocus(hasRingingCall);
+ setMode(mode);
+ }
+
+ if (hasRingingCall) {
+ mRinger.play();
+ } else {
+ mRinger.stop();
+ }
+
+ if (!needsFocus) {
+ setMode(AudioManager.MODE_NORMAL);
+ releaseFocus();
}
}
- private void updateAudioFocusForActiveCall(Call call) {
- Log.v(this, "onForegroundCallChanged, requesting audio focus");
- Context context = TelecommApp.getInstance();
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- audioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- audioManager.setMode(AudioManager.MODE_IN_CALL);
- audioManager.setMicrophoneMute(false);
- audioManager.setSpeakerphoneOn(false);
+ /**
+ * Acquires audio focus.
+ *
+ * @param isForRinging True if this focus is for playing the ringer.
+ */
+ private void acquireFocus(boolean isForRinging) {
+ if (!mHasAudioFocus) {
+ int stream = isForRinging ? AudioManager.STREAM_RING : AudioManager.STREAM_VOICE_CALL;
+
+ AudioManager audioManager = getAudioManager();
+ audioManager.requestAudioFocusForCall(stream, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ audioManager.setMicrophoneMute(false);
+ audioManager.setSpeakerphoneOn(false);
+ mHasAudioFocus = true;
+ }
}
- private void updateAudioFocusForNoCall() {
- Log.v(this, "updateAudioFocusForNoCall, abandoning audio focus");
- Context context = TelecommApp.getInstance().getApplicationContext();
- AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
- audioManager.setMode(AudioManager.MODE_NORMAL);
- audioManager.abandonAudioFocusForCall();
+ /**
+ * Releases focus.
+ */
+ void releaseFocus() {
+ if (mHasAudioFocus) {
+ AudioManager audioManager = getAudioManager();
+
+ // Reset speakerphone and mute in case they were changed by telecomm.
+ audioManager.setMicrophoneMute(false);
+ audioManager.setSpeakerphoneOn(false);
+ audioManager.abandonAudioFocusForCall();
+
+ mHasAudioFocus = false;
+ Log.v(this, "Focus released");
+ }
+
+ }
+
+ /**
+ * Sets the audio mode.
+ *
+ * @param mode Mode constant from AudioManager.MODE_*.
+ */
+ void setMode(int mode) {
+ if (mHasAudioFocus) {
+ AudioManager audioManager = getAudioManager();
+ if (mode != audioManager.getMode()) {
+ Log.v(this, "Audio mode set to %d.", mode);
+ audioManager.setMode(mode);
+ Log.v(this, "Audio mode actually set to %d.", audioManager.getMode());
+ }
+ } else {
+ Log.wtf(this, "Trying to set audio mode to %d without focus.", mode);
+ }
+ }
+
+ /**
+ * Removes the specified call from the list of unanswered incoming calls.
+ *
+ * @param callId The ID of the call.
+ */
+ private void removeFromUnansweredCallIds(String callId) {
+ if (!mUnansweredCallIds.isEmpty()) {
+ // If the call is the top-most call, then no longer disable the ringer.
+ if (callId.equals(mUnansweredCallIds.get(0))) {
+ mIsRingingDisabled = false;
+ }
+
+ mUnansweredCallIds.remove(callId);
+ }
+ }
+
+ /**
+ * Returns the system audio manager.
+ */
+ private AudioManager getAudioManager() {
+ return (AudioManager) TelecommApp.getInstance().getSystemService(Context.AUDIO_SERVICE);
}
}