Add call waiting to Ringer.java.

When an incoming call is in the background, play call waiting instead of
ringer.

Bug: 13674415

Change-Id: I9d450202774c6890794fa318fdab01290b4a7eb2
diff --git a/src/com/android/telecomm/Call.java b/src/com/android/telecomm/Call.java
index 6aae5ca..2db5b4e 100644
--- a/src/com/android/telecomm/Call.java
+++ b/src/com/android/telecomm/Call.java
@@ -461,6 +461,17 @@
         }
     }
 
+    boolean isActive() {
+        switch (mState) {
+            case ACTIVE:
+            case POST_DIAL:
+            case POST_DIAL_WAIT:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     Bundle getExtras() {
         return mExtras;
     }
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index d3b22be..8e81752 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -110,7 +110,8 @@
         mListeners.add(new CallLogManager(app));
         mListeners.add(new PhoneStateBroadcaster());
         mListeners.add(new InCallController());
-        mListeners.add(new Ringer(mCallAudioManager));
+        mListeners.add(
+                new Ringer(mCallAudioManager, this, playerFactory, TelecommApp.getInstance()));
         mListeners.add(new RingbackPlayer(this, playerFactory));
         mListeners.add(new InCallToneMonitor(playerFactory, this));
         mListeners.add(mCallAudioManager);
@@ -278,6 +279,19 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Request to answer a non-existent call %s", call);
         } else {
+            // If the foreground call is not the ringing call and it is currently isActive() or
+            // DIALING, put it on hold before answering the call.
+            if (mForegroundCall != null && mForegroundCall != call &&
+                    (mForegroundCall.isActive() ||
+                     mForegroundCall.getState() == CallState.DIALING)) {
+                Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
+                        mForegroundCall, call);
+                mForegroundCall.hold();
+                // TODO(santoscordon): Wait until we get confirmation of the active call being
+                // on-hold before answering the new call.
+                // TODO(santoscordon): Import logic from CallManager.acceptCall()
+            }
+
             for (CallsManagerListener listener : mListeners) {
                 listener.onIncomingCallAnswered(call);
             }
@@ -514,6 +528,9 @@
      */
     private void addCall(Call call) {
         mCalls.add(call);
+
+        // TODO(santoscordon): Update mForegroundCall prior to invoking
+        // onCallAdded for calls which immediately take the foreground (like the first call).
         for (CallsManagerListener listener : mListeners) {
             listener.onCallAdded(call);
         }
@@ -581,18 +598,25 @@
     private void updateForegroundCall() {
         Call newForegroundCall = null;
         for (Call call : mCalls) {
-            // Incoming ringing calls have priority.
-            if (call.getState() == CallState.RINGING) {
+            // TODO(santoscordon): Foreground-ness needs to be explicitly set. No call, regardless
+            // of its state will be foreground by default and instead the call service should be
+            // notified when its calls enter and exit foreground state. Foreground will mean that
+            // the call should play audio and listen to microphone if it wants.
+
+            // Active calls have priority.
+            if (call.isActive()) {
                 newForegroundCall = call;
                 break;
             }
-            if (call.isAlive()) {
+
+            if (call.isAlive() || call.getState() == CallState.RINGING) {
                 newForegroundCall = call;
-                // Don't break in case there's a ringing call that has priority.
+                // Don't break in case there's an active call that has priority.
             }
         }
 
         if (newForegroundCall != mForegroundCall) {
+            Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall);
             Call oldForegroundCall = mForegroundCall;
             mForegroundCall = newForegroundCall;
 
diff --git a/src/com/android/telecomm/InCallTonePlayer.java b/src/com/android/telecomm/InCallTonePlayer.java
index 258f8b0..aa9655d 100644
--- a/src/com/android/telecomm/InCallTonePlayer.java
+++ b/src/com/android/telecomm/InCallTonePlayer.java
@@ -127,8 +127,10 @@
                     // TODO: fill in
                     throw new IllegalStateException("OTA Call ended NYI.");
                 case TONE_CALL_WAITING:
-                    // TODO: fill in.
-                    throw new IllegalStateException("Call waiting NYI.");
+                    toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
+                    toneVolume = RELATIVE_VOLUME_HIPRI;
+                    toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+                    break;
                 case TONE_CDMA_DROP:
                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
diff --git a/src/com/android/telecomm/Ringer.java b/src/com/android/telecomm/Ringer.java
index fdc9349..f298081 100644
--- a/src/com/android/telecomm/Ringer.java
+++ b/src/com/android/telecomm/Ringer.java
@@ -49,17 +49,29 @@
     private final List<Call> mUnansweredCalls = Lists.newLinkedList();
 
     private final CallAudioManager mCallAudioManager;
-
+    private final CallsManager mCallsManager;
+    private final InCallTonePlayer.Factory mPlayerFactory;
+    private final Context mContext;
     private final Vibrator mVibrator;
 
+    private InCallTonePlayer mCallWaitingPlayer;
+
     /**
      * Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
      */
     private boolean mIsVibrating = false;
 
-    Ringer(CallAudioManager callAudioManager) {
-        mCallAudioManager = callAudioManager;
+    /** Initializes the Ringer. */
+    Ringer(
+            CallAudioManager callAudioManager,
+            CallsManager callsManager,
+            InCallTonePlayer.Factory playerFactory,
+            Context context) {
 
+        mCallAudioManager = callAudioManager;
+        mCallsManager = callsManager;
+        mPlayerFactory = playerFactory;
+        mContext = context;
         // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
         // vibrator object will be isolated from others.
         mVibrator = new SystemVibrator(TelecommApp.getInstance());
@@ -72,14 +84,11 @@
                 Log.wtf(this, "New ringing call is already in list of unanswered calls");
             }
             mUnansweredCalls.add(call);
-            if (mUnansweredCalls.size() == 1) {
-                // Start the ringer if we are the top-most incoming call (the only one in this
-                // case).
-                startRinging();
-            }
+            updateRinging();
         }
     }
 
+
     @Override
     public void onCallRemoved(Call call) {
         removeFromUnansweredCall(call);
@@ -102,11 +111,27 @@
         onRespondedToIncomingCall(call);
     }
 
+    @Override
+    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
+        if (mUnansweredCalls.contains(oldForegroundCall) ||
+                mUnansweredCalls.contains(newForegroundCall)) {
+            updateRinging();
+        }
+    }
+
     private void onRespondedToIncomingCall(Call call) {
         // Only stop the ringer if this call is the top-most incoming call.
-        if (!mUnansweredCalls.isEmpty() && mUnansweredCalls.get(0) == call) {
+        if (getTopMostUnansweredCall() == call) {
             stopRinging();
+            stopCallWaiting();
         }
+
+        // We do not remove the call from mUnansweredCalls until the call state changes from RINGING
+        // or the call is removed. see onCallStateChanged or onCallRemoved.
+    }
+
+    private Call getTopMostUnansweredCall() {
+        return mUnansweredCalls.isEmpty() ? null : mUnansweredCalls.get(0);
     }
 
     /**
@@ -115,44 +140,78 @@
      * present in the list of incoming calls.
      */
     private void removeFromUnansweredCall(Call call) {
-        if (mUnansweredCalls.remove(call)) {
-            if (mUnansweredCalls.isEmpty()) {
-                stopRinging();
-            } else {
-                startRinging();
-            }
+        mUnansweredCalls.remove(call);
+        updateRinging();
+    }
+
+    private void updateRinging() {
+        if (mUnansweredCalls.isEmpty()) {
+            stopRinging();
+            stopCallWaiting();
+        } else {
+            startRingingOrCallWaiting();
         }
     }
 
-    private void startRinging() {
-        AudioManager audioManager = (AudioManager) TelecommApp.getInstance().getSystemService(
-                Context.AUDIO_SERVICE);
-        if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
-            Log.v(this, "startRinging");
-            mCallAudioManager.setIsRinging(true);
-            mRingtonePlayer.play();
-        } else {
-            Log.v(this, "startRinging, skipping because volume is 0");
-        }
+    private void startRingingOrCallWaiting() {
+        Call foregroundCall = mCallsManager.getForegroundCall();
+        Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
 
-        if (shouldVibrate(TelecommApp.getInstance()) && !mIsVibrating) {
-            mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
-                    AudioManager.STREAM_RING);
-            mIsVibrating = true;
+        if (mUnansweredCalls.contains(foregroundCall)) {
+            // The foreground call is one of incoming calls so play the ringer out loud.
+            stopCallWaiting();
+
+            AudioManager audioManager =
+                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+                Log.v(this, "startRingingOrCallWaiting");
+                mCallAudioManager.setIsRinging(true);
+
+                mRingtonePlayer.play();
+
+                if (shouldVibrate(TelecommApp.getInstance()) && !mIsVibrating) {
+                    mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
+                            AudioManager.STREAM_RING);
+                    mIsVibrating = true;
+                }
+            } else {
+                Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
+            }
+        } else {
+            Log.v(this, "Playing call-waiting tone.");
+
+            // All incoming calls are in background so play call waiting.
+            stopRinging();
+
+            if (mCallWaitingPlayer == null) {
+                mCallWaitingPlayer =
+                        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+                mCallWaitingPlayer.startTone();
+            }
         }
     }
 
     private void stopRinging() {
         Log.v(this, "stopRinging");
+
         mRingtonePlayer.stop();
-        // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
-        // focus are voluntary so releasing focus too early is not detrimental.
-        mCallAudioManager.setIsRinging(false);
 
         if (mIsVibrating) {
             mVibrator.cancel();
             mIsVibrating = false;
         }
+
+        // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
+        // focus are voluntary so releasing focus too early is not detrimental.
+        mCallAudioManager.setIsRinging(false);
+    }
+
+    private void stopCallWaiting() {
+        Log.v(this, "stop call waiting.");
+        if (mCallWaitingPlayer != null) {
+            mCallWaitingPlayer.stopTone();
+            mCallWaitingPlayer = null;
+        }
     }
 
     private boolean shouldVibrate(Context context) {