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);
     }
 }