Merge "Fix missed call notification tests"
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index babed6a..1cdd8c1 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -20,7 +20,7 @@
     <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telèfon"</string>
     <string name="unknown" msgid="6878797917991465859">"Desconegut"</string>
     <string name="notification_missedCallTitle" msgid="7554385905572364535">"Trucada perduda"</string>
-    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Trucada de feina perduda"</string>
+    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Trucada perduda de feina"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Trucades perdudes"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> trucades perdudes"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Trucada perduda de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 87f7805..052c5fb 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -20,7 +20,7 @@
     <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
     <string name="unknown" msgid="6878797917991465859">"Белгисиз"</string>
     <string name="notification_missedCallTitle" msgid="7554385905572364535">"Кабыл алынбаган чалуу"</string>
-    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Жумуш боюнча кабыл алынбаган чалуу"</string>
+    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Кабыл алынбай калган чалуу (жумуш)"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Кабыл алынбаган чалуулар"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> кабыл алынбаган чалуу"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> дегенден кабыл алынбаган чалуу"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 66bc610..87bf156 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -20,7 +20,7 @@
     <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Утас"</string>
     <string name="unknown" msgid="6878797917991465859">"Тодорхойгүй"</string>
     <string name="notification_missedCallTitle" msgid="7554385905572364535">"Аваагүй дуудлага"</string>
-    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Аваагүй ажлын дуудлага"</string>
+    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Аваагүй албаны дуудлага"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Аваагүй дуудлагууд"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> аваагүй дуудлага"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>-н аваагүй дуудлага"</string>
diff --git a/src/com/android/server/telecom/BluetoothManager.java b/src/com/android/server/telecom/BluetoothManager.java
index 389d0f6..82c8d47 100644
--- a/src/com/android/server/telecom/BluetoothManager.java
+++ b/src/com/android/server/telecom/BluetoothManager.java
@@ -46,16 +46,26 @@
             new BluetoothProfile.ServiceListener() {
                 @Override
                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
-                    mBluetoothHeadset = (BluetoothHeadset) proxy;
-                    Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
-                    updateBluetoothState();
+                    Log.startSession("BMSL.oSC");
+                    try {
+                        mBluetoothHeadset = (BluetoothHeadset) proxy;
+                        Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
+                        updateBluetoothState();
+                    } finally {
+                        Log.endSession();
+                    }
                 }
 
                 @Override
                 public void onServiceDisconnected(int profile) {
-                    mBluetoothHeadset = null;
-                    Log.v(this, "Lost BluetoothHeadset: " + mBluetoothHeadset);
-                    updateBluetoothState();
+                    Log.startSession("BMSL.oSD");
+                    try {
+                        mBluetoothHeadset = null;
+                        Log.v(this, "Lost BluetoothHeadset: " + mBluetoothHeadset);
+                        updateBluetoothState();
+                    } finally {
+                        Log.endSession();
+                    }
                 }
            };
 
@@ -65,21 +75,26 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            Log.startSession("BM.oR");
+            try {
+                String action = intent.getAction();
 
-            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
-                int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                                          BluetoothHeadset.STATE_DISCONNECTED);
-                Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
-                Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
-                updateBluetoothState();
-            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                int bluetoothHeadsetAudioState =
-                        intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                           BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
-                Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
-                Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
-                updateBluetoothState();
+                if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+                    int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                            BluetoothHeadset.STATE_DISCONNECTED);
+                    Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
+                    Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
+                    updateBluetoothState();
+                } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+                    int bluetoothHeadsetAudioState =
+                            intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                    BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                    Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
+                    Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
+                    updateBluetoothState();
+                }
+            } finally {
+                Log.endSession();
             }
         }
     };
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 7159b49..6680eb6 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -326,12 +326,6 @@
         }
 
         @Override
-        public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-            // The BluetoothPhoneService does not need to respond to changes in foreground calls,
-            // which are always accompanied by call state changes anyway.
-        }
-
-        @Override
         public void onIsConferencedChanged(Call call) {
             /*
              * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 5b3853e..f392e22 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,216 +11,268 @@
  * 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.
+ * limitations under the License
  */
 
 package com.android.server.telecom;
 
-import android.content.Context;
-import android.media.AudioManager;
+import android.annotation.NonNull;
 import android.media.IAudioService;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
+import android.media.ToneGenerator;
 import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.SomeArgs;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
 
-/**
- * This class manages audio modes, streams and other properties.
- */
-@VisibleForTesting
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.LinkedHashSet;
+
 public class CallAudioManager extends CallsManagerListenerBase {
-    private static final int STREAM_NONE = -1;
-
-    private static final String STREAM_DESCRIPTION_NONE = "STEAM_NONE";
-    private static final String STREAM_DESCRIPTION_ALARM = "STEAM_ALARM";
-    private static final String STREAM_DESCRIPTION_BLUETOOTH_SCO = "STREAM_BLUETOOTH_SCO";
-    private static final String STREAM_DESCRIPTION_DTMF = "STREAM_DTMF";
-    private static final String STREAM_DESCRIPTION_MUSIC = "STREAM_MUSIC";
-    private static final String STREAM_DESCRIPTION_NOTIFICATION = "STREAM_NOTIFICATION";
-    private static final String STREAM_DESCRIPTION_RING = "STREAM_RING";
-    private static final String STREAM_DESCRIPTION_SYSTEM = "STREAM_SYSTEM";
-    private static final String STREAM_DESCRIPTION_VOICE_CALL = "STREAM_VOICE_CALL";
-
-    private static final String MODE_DESCRIPTION_INVALID = "MODE_INVALID";
-    private static final String MODE_DESCRIPTION_CURRENT = "MODE_CURRENT";
-    private static final String MODE_DESCRIPTION_NORMAL = "MODE_NORMAL";
-    private static final String MODE_DESCRIPTION_RINGTONE = "MODE_RINGTONE";
-    private static final String MODE_DESCRIPTION_IN_CALL = "MODE_IN_CALL";
-    private static final String MODE_DESCRIPTION_IN_COMMUNICATION = "MODE_IN_COMMUNICATION";
-
-    private static final int MSG_AUDIO_MANAGER_INITIALIZE = 0;
-    private static final int MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL = 2;
-    private static final int MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL = 4;
-    private static final int MSG_AUDIO_MANAGER_SET_MODE = 5;
-
-    private final Handler mAudioManagerHandler = new Handler(Looper.getMainLooper()) {
-
-        private AudioManager mAudioManager;
-
-        @Override
-        public void handleMessage(Message msg) {
-            SomeArgs args = (SomeArgs) msg.obj;
-            int arg2 = 0;
-            try {
-                if (args != null) {
-                    Session subsession = (Session) args.arg1;
-                    Log.continueSession(subsession, "CAM.hM_" + msg.what);
-                    arg2 = (int) args.arg2;
-                }
-                switch (msg.what) {
-                    case MSG_AUDIO_MANAGER_INITIALIZE: {
-                        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-                        break;
-                    }
-                    case MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL: {
-                        mAudioManager.abandonAudioFocusForCall();
-                        break;
-                    }
-                    case MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL: {
-                        int stream = arg2;
-                        mAudioManager.requestAudioFocusForCall(
-                                stream,
-                                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-                        break;
-                    }
-                    case MSG_AUDIO_MANAGER_SET_MODE: {
-                        int newMode = arg2;
-                        int oldMode = mAudioManager.getMode();
-                        Log.v(this, "Request to change audio mode from %s to %s", modeToString(oldMode),
-                                modeToString(newMode));
-
-                        if (oldMode != newMode) {
-                            if (oldMode == AudioManager.MODE_IN_CALL &&
-                                    newMode == AudioManager.MODE_RINGTONE) {
-                                Log.i(this, "Transition from IN_CALL -> RINGTONE."
-                                        + "  Resetting to NORMAL first.");
-                                mAudioManager.setMode(AudioManager.MODE_NORMAL);
-                            }
-                            mAudioManager.setMode(newMode);
-                            synchronized (mLock) {
-                                mMostRecentlyUsedMode = newMode;
-                            }
-                        }
-                        break;
-                    }
-                    default:
-                        break;
-                }
-            } finally {
-                Log.endSession();
-                args.recycle();
-            }
-        }
-    };
 
     public interface AudioServiceFactory {
         IAudioService getAudioService();
     }
 
-    private final Context mContext;
-    private final TelecomSystem.SyncRoot mLock;
-    private final CallsManager mCallsManager;
+    private final String LOG_TAG = CallAudioManager.class.getSimpleName();
+
+    private final LinkedHashSet<Call> mActiveOrDialingCalls;
+    private final LinkedHashSet<Call> mRingingCalls;
+    private final LinkedHashSet<Call> mHoldingCalls;
+    private final Set<Call> mCalls;
+    private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
+
     private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    private final CallAudioModeStateMachine mCallAudioModeStateMachine;
+    private final CallsManager mCallsManager;
+    private final InCallTonePlayer.Factory mPlayerFactory;
+    private final Ringer mRinger;
+    private final RingbackPlayer mRingbackPlayer;
+    private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
 
-    private int mAudioFocusStreamType;
-    private boolean mIsRinging;
-    private boolean mIsTonePlaying;
-    private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
-    private Call mCallToSpeedUpMTAudio = null;
+    private Call mForegroundCall;
+    private boolean mIsTonePlaying = false;
 
-    public CallAudioManager(
-            Context context,
-            TelecomSystem.SyncRoot lock,
+    public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
             CallsManager callsManager,
-            CallAudioRouteStateMachine callAudioRouteStateMachine) {
-        mContext = context;
-        mLock = lock;
-        mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE,
-                setArgs(0)).sendToTarget();
-        mCallsManager = callsManager;
-        mAudioFocusStreamType = STREAM_NONE;
+            CallAudioModeStateMachine callAudioModeStateMachine,
+            InCallTonePlayer.Factory playerFactory,
+            Ringer ringer,
+            RingbackPlayer ringbackPlayer,
+            DtmfLocalTonePlayer dtmfLocalTonePlayer) {
+        mActiveOrDialingCalls = new LinkedHashSet<>();
+        mRingingCalls = new LinkedHashSet<>();
+        mHoldingCalls = new LinkedHashSet<>();
+        mCalls = new HashSet<>();
+        mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
+            put(CallState.ACTIVE, mActiveOrDialingCalls);
+            put(CallState.DIALING, mActiveOrDialingCalls);
+            put(CallState.RINGING, mRingingCalls);
+            put(CallState.ON_HOLD, mHoldingCalls);
+        }};
 
         mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+        mCallAudioModeStateMachine = callAudioModeStateMachine;
+        mCallsManager = callsManager;
+        mPlayerFactory = playerFactory;
+        mRinger = ringer;
+        mRingbackPlayer = ringbackPlayer;
+        mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
+
+        mPlayerFactory.setCallAudioManager(this);
+        mCallAudioModeStateMachine.setCallAudioManager(this);
     }
 
-    private SomeArgs setArgs(int arg) {
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = Log.createSubsession();
-        args.arg2 = arg;
-        return args;
-    }
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        if (call.getParentCall() != null) {
+            // No audio management for calls in a conference.
+            return;
+        }
+        Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
+                CallState.toString(oldState), CallState.toString(newState));
 
-    @VisibleForTesting
-    public CallAudioState getCallAudioState() {
-        return mCallAudioRouteStateMachine.getCurrentCallAudioState();
+        if (mCallStateToCalls.get(oldState) != null) {
+            mCallStateToCalls.get(oldState).remove(call);
+        }
+        if (mCallStateToCalls.get(newState) != null) {
+            mCallStateToCalls.get(newState).add(call);
+        }
+
+        updateForegroundCall();
+        if (newState == CallState.DISCONNECTED) {
+            playToneForDisconnectedCall(call);
+        }
+
+        onCallLeavingState(call, oldState);
+        onCallEnteringState(call, newState);
     }
 
     @Override
     public void onCallAdded(Call call) {
-        Log.v(this, "onCallAdded");
-        onCallUpdated(call);
-
-        if (hasFocus() && getForegroundCall() == call) {
-            if (!call.isIncoming()) {
-                // Unmute new outgoing call.
-                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
-                        CallAudioRouteStateMachine.MUTE_OFF);
-            }
+        if (call.getParentCall() != null) {
+            return; // Don't do audio handling for calls in a conference.
         }
+
+        if (mCalls.contains(call)) {
+            Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
+            return; // No guarantees that the same call won't get added twice.
+        }
+
+        Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
+                CallState.toString(call.getState()));
+
+        if (mCallStateToCalls.get(call.getState()) != null) {
+            mCallStateToCalls.get(call.getState()).add(call);
+        }
+        updateForegroundCall();
+        mCalls.add(call);
+
+        onCallEnteringState(call, call.getState());
     }
 
     @Override
     public void onCallRemoved(Call call) {
-        Log.v(this, "onCallRemoved");
+        if (call.getParentCall() != null) {
+            return; // Don't do audio handling for calls in a conference.
+        }
+
+        if (!mCalls.contains(call)) {
+            return; // No guarantees that the same call won't get removed twice.
+        }
+
+        Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
+                CallState.toString(call.getState()));
+
+        if (mCallStateToCalls.get(call.getState()) != null) {
+            mCallStateToCalls.get(call.getState()).remove(call);
+        }
+
+        updateForegroundCall();
+        mCalls.remove(call);
+
+        onCallLeavingState(call, call.getState());
+
         if (mCallsManager.getCalls().isEmpty()) {
             Log.v(this, "all calls removed, resetting system audio to default state");
             mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.REINITIALIZE);
         }
-
-        // If we didn't already have focus, there's nothing to do.
-        if (hasFocus()) {
-            updateAudioStreamAndMode(call);
-        }
-    }
-
-    @Override
-    public void onCallStateChanged(Call call, int oldState, int newState) {
-        Log.v(this, "onCallStateChanged : oldState = %d, newState = %d", oldState, newState);
-        onCallUpdated(call);
     }
 
     @Override
     public void onIncomingCallAnswered(Call call) {
-        Log.v(this, "onIncomingCallAnswered");
-
-        if (mCallsManager.getCalls().size() == 1) {
-            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
-                    CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.HAS_FOCUS);
-        }
+        // This is called after the UI answers the call, but before the connection service
+        // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
 
         if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
-            Log.v(this, "Speed up audio setup for IMS MT call.");
-            mCallToSpeedUpMTAudio = call;
-            updateAudioStreamAndMode(call);
+            if (mForegroundCall == call) {
+                Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
+                        "an active in-call audio state before connection service has " +
+                        "connected the call.");
+                if (mCallStateToCalls.get(call.getState()) != null) {
+                    mCallStateToCalls.get(call.getState()).remove(call);
+                }
+                mActiveOrDialingCalls.add(call);
+                mCallAudioModeStateMachine.sendMessage(
+                        CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
+                        makeArgsForModeStateMachine());
+            }
+        }
+
+        if (mRingingCalls.size() == 0) {
+            mRinger.stopRinging();
+            mRinger.stopCallWaiting();
         }
     }
 
     @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-        onCallUpdated(newForegroundCall);
-        // Ensure that the foreground call knows about the latest audio state.
-        updateAudioForForegroundCall();
+    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
+        if (videoProfile == null) {
+            return;
+        }
+
+        if (call != mForegroundCall) {
+            // We only play tones for foreground calls.
+            return;
+        }
+
+        int previousVideoState = call.getVideoState();
+        int newVideoState = videoProfile.getVideoState();
+        Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
+                .videoStateToString(newVideoState));
+
+        boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
+                VideoProfile.isReceptionEnabled(newVideoState);
+
+        if (isUpgradeRequest) {
+            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
+        }
     }
 
     @Override
     public void onIsVoipAudioModeChanged(Call call) {
-        updateAudioStreamAndMode(call);
+        if (call != mForegroundCall) {
+            return;
+        }
+        mCallAudioModeStateMachine.sendMessage(
+                CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
+                makeArgsForModeStateMachine());
+    }
+
+    @Override
+    public void onRingbackRequested(Call call, boolean shouldRingback) {
+        if (call == mForegroundCall && shouldRingback) {
+            mRingbackPlayer.startRingbackForCall(call);
+        } else {
+            mRingbackPlayer.stopRingbackForCall(call);
+        }
+    }
+
+    @Override
+    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
+        // This gets called after the UI rejects a call but before the CS processes the rejection.
+        // Will get called before the state change from ringing to not ringing.
+
+        if (mRingingCalls.size() == 0 || call == mRingingCalls.iterator().next()) {
+            mRinger.stopRinging();
+            mRinger.stopCallWaiting();
+        }
+    }
+
+    @Override
+    public void onIsConferencedChanged(Call call) {
+        // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
+        Call parentCall = call.getParentCall();
+        if (parentCall == null) {
+            // Indicates that the call should be tracked for audio purposes. Treat it as if it were
+            // just added.
+            Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
+                            " now be tracked by CallAudioManager.");
+            onCallAdded(call);
+        } else {
+            // The call joined a conference, so stop tracking it.
+            if (mCallStateToCalls.get(call.getState()) != null) {
+                mCallStateToCalls.get(call.getState()).remove(call);
+            }
+
+            updateForegroundCall();
+            mCalls.remove(call);
+        }
+    }
+
+    public CallAudioState getCallAudioState() {
+        return mCallAudioRouteStateMachine.getCurrentCallAudioState();
+    }
+
+    public Call getForegroundCall() {
+        if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
+            return mForegroundCall;
+        }
+        return null;
     }
 
     void toggleMute() {
@@ -229,10 +281,6 @@
     }
 
     void mute(boolean shouldMute) {
-        if (!hasFocus()) {
-            return;
-        }
-
         Log.v(this, "mute, shouldMute: %b", shouldMute);
 
         // Don't mute if there are any emergency calls.
@@ -278,239 +326,232 @@
         }
     }
 
-    /**
-     * Sets the audio stream and mode based on whether a call is ringing.
-     *
-     * @param call The call which changed ringing state.
-     * @param isRinging {@code true} if the call is ringing, {@code false} otherwise.
-     */
+    void silenceRingers() {
+        for (Call call : mRingingCalls) {
+            call.silence();
+        }
+
+        mRingingCalls.clear();
+        mRinger.stopRinging();
+        mRinger.stopCallWaiting();
+        mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+                makeArgsForModeStateMachine());
+    }
+
+    void startRinging() {
+        mRinger.startRinging(mForegroundCall);
+    }
+
+    void startCallWaiting() {
+        mRinger.startCallWaiting(mRingingCalls.iterator().next());
+    }
+
+    void stopRinging() {
+        mRinger.stopRinging();
+    }
+
+    void stopCallWaiting() {
+        mRinger.stopCallWaiting();
+    }
+
+    void setCallAudioRouteFocusState(int focusState) {
+        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("Active or dialing calls:");
+        pw.increaseIndent();
+        dumpCallsInCollection(pw, mActiveOrDialingCalls);
+        pw.decreaseIndent();
+
+        pw.println("Ringing calls:");
+        pw.increaseIndent();
+        dumpCallsInCollection(pw, mRingingCalls);
+        pw.decreaseIndent();
+
+        pw.println("Holding calls:");
+        pw.increaseIndent();
+        dumpCallsInCollection(pw, mHoldingCalls);
+        pw.decreaseIndent();
+
+        pw.println("Foreground call:");
+        pw.println(mForegroundCall);
+    }
+
     @VisibleForTesting
-    public void setIsRinging(Call call, boolean isRinging) {
-        if (mIsRinging != isRinging) {
-            Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call);
-            mIsRinging = isRinging;
-            updateAudioStreamAndMode(call);
+    public void setIsTonePlaying(boolean isTonePlaying) {
+        mIsTonePlaying = isTonePlaying;
+        mCallAudioModeStateMachine.sendMessage(
+                isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
+                        : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
+                makeArgsForModeStateMachine());
+    }
+
+    private void onCallLeavingState(Call call, int state) {
+        switch (state) {
+            case CallState.ACTIVE:
+                onCallLeavingActiveOrDialing();
+                break;
+            case CallState.RINGING:
+                onCallLeavingRinging();
+                break;
+            case CallState.ON_HOLD:
+                onCallLeavingHold();
+                break;
+            case CallState.DIALING:
+                stopRingbackForCall(call);
+                onCallLeavingActiveOrDialing();
         }
     }
 
-    /**
-     * Sets the tone playing status. Some tones can play even when there are no live calls and this
-     * status indicates that we should keep audio focus even for tones that play beyond the life of
-     * calls.
-     *
-     * @param isPlayingNew The status to set.
-     */
-    void setIsTonePlaying(boolean isPlayingNew) {
-        if (mIsTonePlaying != isPlayingNew) {
-            Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
-            mIsTonePlaying = isPlayingNew;
-            updateAudioStreamAndMode();
+    private void onCallEnteringState(Call call, int state) {
+        switch (state) {
+            case CallState.ACTIVE:
+                onCallEnteringActiveOrDialing();
+                break;
+            case CallState.RINGING:
+                onCallEnteringRinging();
+                break;
+            case CallState.ON_HOLD:
+                onCallEnteringHold();
+                break;
+            case CallState.DIALING:
+                onCallEnteringActiveOrDialing();
+                playRingbackForCall(call);
+                break;
         }
     }
 
-    private void onCallUpdated(Call call) {
-        updateAudioStreamAndMode(call);
-        if (call != null && call.getState() == CallState.ACTIVE &&
-                            call == mCallToSpeedUpMTAudio) {
-            mCallToSpeedUpMTAudio = null;
+    private void onCallLeavingActiveOrDialing() {
+        if (mActiveOrDialingCalls.size() == 0) {
+            mCallAudioModeStateMachine.sendMessage(
+                    CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
+                    makeArgsForModeStateMachine());
         }
     }
 
-    private void updateAudioStreamAndMode() {
-        updateAudioStreamAndMode(null /* call */);
+    private void onCallLeavingRinging() {
+        if (mRingingCalls.size() == 0) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+                    makeArgsForModeStateMachine());
+        }
     }
 
-    private void updateAudioStreamAndMode(Call callToUpdate) {
-        Log.i(this, "updateAudioStreamAndMode :  mIsRinging: %b, mIsTonePlaying: %b, call: %s",
-                mIsRinging, mIsTonePlaying, callToUpdate);
+    private void onCallLeavingHold() {
+        if (mHoldingCalls.size() == 0) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
+                    makeArgsForModeStateMachine());
+        }
+    }
 
-        if (mIsRinging) {
-            Log.i(this, "updateAudioStreamAndMode : ringing");
-            requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
+    private void onCallEnteringActiveOrDialing() {
+        if (mActiveOrDialingCalls.size() == 1) {
+            mCallAudioModeStateMachine.sendMessage(
+                    CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void onCallEnteringRinging() {
+        if (mRingingCalls.size() == 1) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void onCallEnteringHold() {
+        if (mHoldingCalls.size() == 1) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void updateForegroundCall() {
+        Call oldForegroundCall = mForegroundCall;
+        if (mActiveOrDialingCalls.size() > 0) {
+            mForegroundCall = mActiveOrDialingCalls.iterator().next();
+        } else if (mRingingCalls.size() > 0) {
+            mForegroundCall = mRingingCalls.iterator().next();
+        } else if (mHoldingCalls.size() > 0) {
+            mForegroundCall = mHoldingCalls.iterator().next();
         } else {
-            Call foregroundCall = getForegroundCall();
-            Call waitingForAccountSelectionCall = mCallsManager
-                    .getFirstCallWithState(CallState.SELECT_PHONE_ACCOUNT);
-            Call call = mCallsManager.getForegroundCall();
-            if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) {
-                Log.v(this, "updateAudioStreamAndMode : no foreground, speeding up MT audio.");
-                requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL,
-                                                         AudioManager.MODE_IN_CALL);
-            } else if (foregroundCall != null && !foregroundCall.isDisconnected() &&
-                    waitingForAccountSelectionCall == null) {
-                // In the case where there is a call that is waiting for account selection,
-                // this will fall back to abandonAudioFocus() below, which temporarily exits
-                // the in-call audio mode. This is to allow TalkBack to speak the "Call with"
-                // dialog information at media volume as opposed to through the earpiece.
-                // Once exiting the "Call with" dialog, the audio focus will return to an in-call
-                // audio mode when this method (updateAudioStreamAndMode) is called again.
-                int mode = foregroundCall.getIsVoipAudioMode() ?
-                        AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
-                Log.v(this, "updateAudioStreamAndMode : foreground");
-                requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
-            } else if (mIsTonePlaying) {
-                // There is no call, however, we are still playing a tone, so keep focus.
-                // Since there is no call from which to determine the mode, use the most
-                // recently used mode instead.
-                Log.v(this, "updateAudioStreamAndMode : tone playing");
-                requestAudioFocusAndSetMode(
-                        AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode);
-            } else if (!hasRingingForegroundCall() && mCallsManager.hasOnlyDisconnectedCalls()) {
-                Log.v(this, "updateAudioStreamAndMode : no ringing call");
-                abandonAudioFocus();
-            } else {
-                // mIsRinging is false, but there is a foreground ringing call present. Don't
-                // abandon audio focus immediately to prevent audio focus from getting lost between
-                // the time it takes for the foreground call to transition from RINGING to ACTIVE/
-                // DISCONNECTED. When the call eventually transitions to the next state, audio
-                // focus will be correctly abandoned by the if clause above.
+            mForegroundCall = null;
+        }
+
+        if (mForegroundCall != oldForegroundCall) {
+            mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
+        }
+    }
+
+    @NonNull
+    private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
+        return new CallAudioModeStateMachine.MessageArgs(
+                mActiveOrDialingCalls.size() > 0,
+                mRingingCalls.size() > 0,
+                mHoldingCalls.size() > 0,
+                mIsTonePlaying,
+                mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
+                Log.createSubsession());
+    }
+
+    private void playToneForDisconnectedCall(Call call) {
+        if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
+            Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
+                    " and there is another call.");
+            return;
+        }
+
+        if (call.getDisconnectCause() != null) {
+            int toneToPlay = InCallTonePlayer.TONE_INVALID;
+
+            Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
+
+            switch(call.getDisconnectCause().getTone()) {
+                case ToneGenerator.TONE_SUP_BUSY:
+                    toneToPlay = InCallTonePlayer.TONE_BUSY;
+                    break;
+                case ToneGenerator.TONE_SUP_CONGESTION:
+                    toneToPlay = InCallTonePlayer.TONE_CONGESTION;
+                    break;
+                case ToneGenerator.TONE_CDMA_REORDER:
+                    toneToPlay = InCallTonePlayer.TONE_REORDER;
+                    break;
+                case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
+                    toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
+                    break;
+                case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
+                    toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
+                    break;
+                case ToneGenerator.TONE_SUP_ERROR:
+                    toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
+                    break;
+                case ToneGenerator.TONE_PROP_PROMPT:
+                    toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
+                    break;
+            }
+
+            Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
+
+            if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
+                mPlayerFactory.createPlayer(toneToPlay).startTone();
             }
         }
     }
 
-    private void requestAudioFocusAndSetMode(int stream, int mode) {
-        Log.v(this, "requestAudioFocusAndSetMode : stream: %s -> %s, mode: %s",
-                streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream),
-                modeToString(mode));
-        Preconditions.checkState(stream != STREAM_NONE);
-
-        // Even if we already have focus, if the stream is different we update audio manager to give
-        // it a hint about the purpose of our focus.
-        if (mAudioFocusStreamType != stream) {
-            Log.i(this, "requestAudioFocusAndSetMode : requesting stream: %s -> %s",
-                    streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream));
-            mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL,
-                    setArgs(stream)).sendToTarget();
-        }
-        mAudioFocusStreamType = stream;
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.HAS_FOCUS);
-
-        setMode(mode);
-    }
-
-    private void abandonAudioFocus() {
-        if (hasFocus()) {
-            setMode(AudioManager.MODE_NORMAL);
-            Log.v(this, "abandoning audio focus");
-            mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL,
-                    setArgs(0)).sendToTarget();
-            mAudioFocusStreamType = STREAM_NONE;
-            mCallToSpeedUpMTAudio = null;
-        }
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.NO_FOCUS);
-    }
-
-    /**
-     * Sets the audio mode.
-     *
-     * @param newMode Mode constant from AudioManager.MODE_*.
-     */
-    private void setMode(int newMode) {
-        Preconditions.checkState(hasFocus());
-        mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_SET_MODE,
-                setArgs(newMode)).sendToTarget();
-    }
-
-    private void updateAudioForForegroundCall() {
-        Call call = mCallsManager.getForegroundCall();
-        if (call != null && call.getConnectionService() != null) {
-            call.getConnectionService().onCallAudioStateChanged(call,
-                    mCallAudioRouteStateMachine.getCurrentCallAudioState());
+    private void playRingbackForCall(Call call) {
+        if (call == mForegroundCall && call.isRingbackRequested()) {
+            mRingbackPlayer.startRingbackForCall(call);
         }
     }
 
-    /**
-     * Returns the current foreground call in order to properly set the audio mode.
-     */
-    private Call getForegroundCall() {
-        Call call = mCallsManager.getForegroundCall();
+    private void stopRingbackForCall(Call call) {
+        mRingbackPlayer.stopRingbackForCall(call);
+    }
 
-        // We ignore any foreground call that is in the ringing state because we deal with ringing
-        // calls exclusively through the mIsRinging variable set by {@link Ringer}.
-        if (call != null && call.getState() == CallState.RINGING) {
-            return null;
+    private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
+        for (Call call : calls) {
+            if (call != null) pw.println(call.getId());
         }
-
-        return call;
-    }
-
-    private boolean hasRingingForegroundCall() {
-        Call call = mCallsManager.getForegroundCall();
-        return call != null && call.getState() == CallState.RINGING;
-    }
-
-    private boolean hasFocus() {
-        return mAudioFocusStreamType != STREAM_NONE;
-    }
-
-    /**
-     * Translates an {@link AudioManager} stream type to a human-readable string description.
-     *
-     * @param streamType The stream type.
-     * @return Human readable description.
-     */
-    private String streamTypeToString(int streamType) {
-        switch (streamType) {
-            case STREAM_NONE:
-                return STREAM_DESCRIPTION_NONE;
-            case AudioManager.STREAM_ALARM:
-                return STREAM_DESCRIPTION_ALARM;
-            case AudioManager.STREAM_BLUETOOTH_SCO:
-                return STREAM_DESCRIPTION_BLUETOOTH_SCO;
-            case AudioManager.STREAM_DTMF:
-                return STREAM_DESCRIPTION_DTMF;
-            case AudioManager.STREAM_MUSIC:
-                return STREAM_DESCRIPTION_MUSIC;
-            case AudioManager.STREAM_NOTIFICATION:
-                return STREAM_DESCRIPTION_NOTIFICATION;
-            case AudioManager.STREAM_RING:
-                return STREAM_DESCRIPTION_RING;
-            case AudioManager.STREAM_SYSTEM:
-                return STREAM_DESCRIPTION_SYSTEM;
-            case AudioManager.STREAM_VOICE_CALL:
-                return STREAM_DESCRIPTION_VOICE_CALL;
-            default:
-                return "STEAM_OTHER_" + streamType;
-        }
-    }
-
-    /**
-     * Translates an {@link AudioManager} mode into a human readable string.
-     *
-     * @param mode The mode.
-     * @return The string.
-     */
-    private String modeToString(int mode) {
-        switch (mode) {
-            case AudioManager.MODE_INVALID:
-                return MODE_DESCRIPTION_INVALID;
-            case AudioManager.MODE_CURRENT:
-                return MODE_DESCRIPTION_CURRENT;
-            case AudioManager.MODE_NORMAL:
-                return MODE_DESCRIPTION_NORMAL;
-            case AudioManager.MODE_RINGTONE:
-                return MODE_DESCRIPTION_RINGTONE;
-            case AudioManager.MODE_IN_CALL:
-                return MODE_DESCRIPTION_IN_CALL;
-            case AudioManager.MODE_IN_COMMUNICATION:
-                return MODE_DESCRIPTION_IN_COMMUNICATION;
-            default:
-                return "MODE_OTHER_" + mode;
-        }
-    }
-
-    /**
-     * Dumps the state of the {@link CallAudioManager}.
-     *
-     * @param pw The {@code IndentingPrintWriter} to write the state to.
-     */
-    public void dump(IndentingPrintWriter pw) {
-        pw.println("mAudioState: " + mCallAudioRouteStateMachine.getCurrentCallAudioState());
-        pw.println("mAudioFocusStreamType: " + streamTypeToString(mAudioFocusStreamType));
-        pw.println("mIsRinging: " + mIsRinging);
-        pw.println("mIsTonePlaying: " + mIsTonePlaying);
-        pw.println("mMostRecentlyUsedMode: " + modeToString(mMostRecentlyUsedMode));
     }
 }
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
new file mode 100644
index 0000000..a0b665f
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -0,0 +1,493 @@
+/*
+ * 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.server.telecom;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Message;
+import android.util.SparseArray;
+
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+public class CallAudioModeStateMachine extends StateMachine {
+    public static class MessageArgs {
+        public boolean hasActiveCalls;
+        public boolean hasRingingCalls;
+        public boolean hasHoldingCalls;
+        public boolean isTonePlaying;
+        public boolean foregroundCallIsVoip;
+        public Session session;
+
+        public MessageArgs(boolean hasActiveCalls, boolean hasRingingCalls, boolean hasHoldingCalls,
+                boolean isTonePlaying, boolean foregroundCallIsVoip, Session session) {
+            this.hasActiveCalls = hasActiveCalls;
+            this.hasRingingCalls = hasRingingCalls;
+            this.hasHoldingCalls = hasHoldingCalls;
+            this.isTonePlaying = isTonePlaying;
+            this.foregroundCallIsVoip = foregroundCallIsVoip;
+            this.session = session;
+        }
+
+        @Override
+        public String toString() {
+            return "MessageArgs{" +
+                    "hasActiveCalls=" + hasActiveCalls +
+                    ", hasRingingCalls=" + hasRingingCalls +
+                    ", hasHoldingCalls=" + hasHoldingCalls +
+                    ", isTonePlaying=" + isTonePlaying +
+                    ", foregroundCallIsVoip=" + foregroundCallIsVoip +
+                    ", session=" + session +
+                    '}';
+        }
+    }
+
+    public static final int INITIALIZE = 1;
+    // These ENTER_*_FOCUS commands are for testing.
+    public static final int ENTER_CALL_FOCUS_FOR_TESTING = 2;
+    public static final int ENTER_COMMS_FOCUS_FOR_TESTING = 3;
+    public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
+    public static final int ABANDON_FOCUS_FOR_TESTING = 5;
+
+    public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
+    public static final int NO_MORE_RINGING_CALLS = 1002;
+    public static final int NO_MORE_HOLDING_CALLS = 1003;
+
+    public static final int NEW_ACTIVE_OR_DIALING_CALL = 2001;
+    public static final int NEW_RINGING_CALL = 2002;
+    public static final int NEW_HOLDING_CALL = 2003;
+    public static final int MT_AUDIO_SPEEDUP_FOR_RINGING_CALL = 2004;
+
+    public static final int TONE_STARTED_PLAYING = 3001;
+    public static final int TONE_STOPPED_PLAYING = 3002;
+
+    public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
+
+    private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
+        put(ENTER_CALL_FOCUS_FOR_TESTING, "ENTER_CALL_FOCUS_FOR_TESTING");
+        put(ENTER_COMMS_FOCUS_FOR_TESTING, "ENTER_COMMS_FOCUS_FOR_TESTING");
+        put(ENTER_RING_FOCUS_FOR_TESTING, "ENTER_RING_FOCUS_FOR_TESTING");
+        put(ABANDON_FOCUS_FOR_TESTING, "ABANDON_FOCUS_FOR_TESTING");
+        put(NO_MORE_ACTIVE_OR_DIALING_CALLS, "NO_MORE_ACTIVE_OR_DIALING_CALLS");
+        put(NO_MORE_RINGING_CALLS, "NO_MORE_RINGING_CALLS");
+        put(NO_MORE_HOLDING_CALLS, "NO_MORE_HOLDING_CALLS");
+        put(NEW_ACTIVE_OR_DIALING_CALL, "NEW_ACTIVE_OR_DIALING_CALL");
+        put(NEW_RINGING_CALL, "NEW_RINGING_CALL");
+        put(NEW_HOLDING_CALL, "NEW_HOLDING_CALL");
+        put(MT_AUDIO_SPEEDUP_FOR_RINGING_CALL, "MT_AUDIO_SPEEDUP_FOR_RINGING_CALL");
+        put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
+        put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
+        put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
+    }};
+
+    private class BaseState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case ENTER_CALL_FOCUS_FOR_TESTING:
+                    transitionTo(mSimCallFocusState);
+                    return HANDLED;
+                case ENTER_COMMS_FOCUS_FOR_TESTING:
+                    transitionTo(mVoipCallFocusState);
+                    return HANDLED;
+                case ENTER_RING_FOCUS_FOR_TESTING:
+                    transitionTo(mRingingFocusState);
+                    return HANDLED;
+                case ABANDON_FOCUS_FOR_TESTING:
+                    transitionTo(mUnfocusedState);
+                    return HANDLED;
+                case INITIALIZE:
+                    mIsInitialized = true;
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class UnfocusedState extends BaseState {
+        @Override
+        public void enter() {
+            if (mIsInitialized) {
+                Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
+                mAudioManager.abandonAudioFocusForCall();
+                mAudioManager.setMode(AudioManager.MODE_NORMAL);
+
+                mMostRecentMode = AudioManager.MODE_NORMAL;
+                mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    Log.w(LOG_TAG, "Newly active/dialing call appeared from an unfocused state. " +
+                            "Args are:\n" + args.toString());
+                    transitionTo(args.foregroundCallIsVoip
+                            ? mVoipCallFocusState : mSimCallFocusState);
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    transitionTo(mRingingFocusState);
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // This really shouldn't happen, but transition to the focused state anyway.
+                    Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
+                            " Args are: \n" + args.toString());
+                    transitionTo(mOtherFocusState);
+                    return HANDLED;
+                case TONE_STARTED_PLAYING:
+                    // This shouldn't happen either, but perform the action anyway.
+                    Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
+                            + args.toString());
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class RingingFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering RINGING state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            if (mMostRecentMode == AudioManager.MODE_IN_CALL) {
+                // Preserving behavior from the old CallAudioManager.
+                Log.i(LOG_TAG, "Transition from IN_CALL -> RINGTONE."
+                        + "  Resetting to NORMAL first.");
+                mAudioManager.setMode(AudioManager.MODE_NORMAL);
+            }
+            mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+
+            mCallAudioManager.startRinging();
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public void exit() {
+            // Audio mode and audio stream will be set by the next state.
+            mCallAudioManager.stopRinging();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Do nothing. Loss of an active call should not impact ringer.
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing and keep ringing.
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // If there are active or holding calls, switch to the appropriate focus.
+                    // Otherwise abandon focus.
+                    if (args.hasActiveCalls) {
+                        if (args.foregroundCallIsVoip) {
+                            transitionTo(mVoipCallFocusState);
+                        } else {
+                            transitionTo(mSimCallFocusState);
+                        }
+                    } else if (args.hasHoldingCalls || args.isTonePlaying) {
+                        transitionTo(mOtherFocusState);
+                    } else {
+                        transitionTo(mUnfocusedState);
+                    }
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    // If a call becomes active suddenly, give it priority over ringing.
+                    transitionTo(args.foregroundCallIsVoip
+                            ? mVoipCallFocusState : mSimCallFocusState);
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " +
+                            "ringing state.");
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // This really shouldn't happen, but transition to the focused state anyway.
+                    Log.w(LOG_TAG, "Call was surprisingly put into hold while ringing." +
+                            " Args are: " + args.toString());
+                    transitionTo(mOtherFocusState);
+                    return HANDLED;
+                case MT_AUDIO_SPEEDUP_FOR_RINGING_CALL:
+                    // This happens when an IMS call is answered by the in-call UI. Special case
+                    // that we have to deal with for some reason.
+
+                    // VOIP calls should never invoke this mechanism, so transition directly to
+                    // the sim call focus state.
+                    transitionTo(mSimCallFocusState);
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class SimCallFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+            mMostRecentMode = AudioManager.MODE_IN_CALL;
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Switch to either ringing, holding, or inactive
+                    if (args.hasHoldingCalls || args.isTonePlaying) {
+                        transitionTo(mOtherFocusState);
+                    } else if (args.hasRingingCalls) {
+                        transitionTo(mRingingFocusState);
+                    } else {
+                        transitionTo(mUnfocusedState);
+                    }
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // Don't transition state, but stop any call-waiting tones that may have been
+                    // playing.
+                    mCallAudioManager.stopCallWaiting();
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    // Do nothing. Already active.
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    // Don't make a call ring over an active call, but do play a call waiting tone.
+                    mCallAudioManager.startCallWaiting();
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // Don't do anything now. Putting an active call on hold will be handled when
+                    // NO_MORE_ACTIVE_CALLS is processed.
+                    return HANDLED;
+                case FOREGROUND_VOIP_MODE_CHANGE:
+                    if (args.foregroundCallIsVoip) {
+                        transitionTo(mVoipCallFocusState);
+                    }
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class VoipCallFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+            mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Switch to either ringing, holding, or inactive
+                    if (args.hasHoldingCalls || args.isTonePlaying) {
+                        transitionTo(mOtherFocusState);
+                    } else if (args.hasRingingCalls) {
+                        transitionTo(mRingingFocusState);
+                    } else {
+                        transitionTo(mUnfocusedState);
+                    }
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // Don't transition state, but stop any call-waiting tones that may have been
+                    // playing.
+                    mCallAudioManager.stopCallWaiting();
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    // Do nothing. Already active.
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    // Don't make a call ring over an active call, but do play a call waiting tone.
+                    mCallAudioManager.startCallWaiting();
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // Don't do anything now. Putting an active call on hold will be handled when
+                    // NO_MORE_ACTIVE_CALLS is processed.
+                    return HANDLED;
+                case FOREGROUND_VOIP_MODE_CHANGE:
+                    if (!args.foregroundCallIsVoip) {
+                        transitionTo(mSimCallFocusState);
+                    }
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    /**
+     * This class is used for calls on hold and end-of-call tones.
+     */
+    private class OtherFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            mAudioManager.setMode(mMostRecentMode);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_HOLDING_CALLS:
+                    if (args.hasActiveCalls) {
+                        transitionTo(args.foregroundCallIsVoip
+                                ? mVoipCallFocusState : mSimCallFocusState);
+                    } else if (args.hasRingingCalls) {
+                        transitionTo(mRingingFocusState);
+                    } else if (!args.isTonePlaying) {
+                        transitionTo(mUnfocusedState);
+                    }
+                    // Do nothing if a tone is playing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    transitionTo(args.foregroundCallIsVoip
+                            ? mVoipCallFocusState : mSimCallFocusState);
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    // Apparently this is current behavior. Should this be the case?
+                    transitionTo(mRingingFocusState);
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // If there are no more ringing calls in this state, then stop any call-waiting
+                    // tones that may be playing.
+                    mCallAudioManager.stopCallWaiting();
+                    return HANDLED;
+                case TONE_STOPPED_PLAYING:
+                    if (!args.hasActiveCalls && !args.hasRingingCalls && !args.hasHoldingCalls
+                            && !args.isTonePlaying) {
+                        transitionTo(mUnfocusedState);
+                    }
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private static final String LOG_TAG = CallAudioModeStateMachine.class.getSimpleName();
+
+    private final State mUnfocusedState = new UnfocusedState();
+    private final State mRingingFocusState = new RingingFocusState();
+    private final State mSimCallFocusState = new SimCallFocusState();
+    private final State mVoipCallFocusState = new VoipCallFocusState();
+    private final State mOtherFocusState = new OtherFocusState();
+
+    private final Context mContext;
+    private final AudioManager mAudioManager;
+    private CallAudioManager mCallAudioManager;
+
+    private int mMostRecentMode;
+    private boolean mIsInitialized = false;
+
+    public CallAudioModeStateMachine(Context context) {
+        super(CallAudioModeStateMachine.class.getSimpleName());
+        mContext = context;
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mMostRecentMode = AudioManager.MODE_NORMAL;
+
+        addState(mUnfocusedState);
+        addState(mRingingFocusState);
+        addState(mSimCallFocusState);
+        addState(mVoipCallFocusState);
+        addState(mOtherFocusState);
+        setInitialState(mUnfocusedState);
+        start();
+        sendMessage(INITIALIZE);
+    }
+
+    public void setCallAudioManager(CallAudioManager callAudioManager) {
+        mCallAudioManager = callAudioManager;
+    }
+
+    @Override
+    protected void onPreHandleMessage(Message msg) {
+        if (msg.obj != null && msg.obj instanceof MessageArgs) {
+            Log.continueSession(((MessageArgs) msg.obj).session, "CAMSM.pM_" + msg.what);
+            Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what));
+        } else {
+            Log.w(LOG_TAG, "Message sent must be of type nonnull MessageArgs, but got " +
+                    (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName()));
+            Log.w(LOG_TAG, "The message was of code %d = %s",
+                    msg.what, MESSAGE_CODE_TO_NAME.get(msg.what));
+        }
+    }
+
+    @Override
+    protected void onPostHandleMessage(Message msg) {
+        Log.endSession();
+    }
+
+    @Override
+    protected void unhandledMessage(Message msg) {
+        // TODO: figure out if anything actually needs to be done here.
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 32b4294..8a659e4 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -81,7 +81,6 @@
                 ConnectionServiceWrapper newService);
         void onIncomingCallAnswered(Call call);
         void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
-        void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
         void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
         void onRingbackRequested(Call call, boolean ringback);
         void onIsConferencedChanged(Call call);
@@ -169,12 +168,6 @@
 
     private boolean mCanAddCall = true;
 
-    /**
-     * The call the user is currently interacting with. This is the call that should have audio
-     * focus and be visible in the in-call UI.
-     */
-    private Call mForegroundCall;
-
     private Runnable mStopTone;
 
     /**
@@ -192,7 +185,8 @@
             InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
             BluetoothManager bluetoothManager,
-            WiredHeadsetManager wiredHeadsetManager) {
+            WiredHeadsetManager wiredHeadsetManager,
+            SystemStateProvider systemStateProvider) {
         mContext = context;
         mLock = lock;
         mContactsAsyncHelper = contactsAsyncHelper;
@@ -203,6 +197,8 @@
         mWiredHeadsetManager = wiredHeadsetManager;
         mBluetoothManager = bluetoothManager;
         mDockManager = new DockManager(context);
+
+        mDtmfLocalTonePlayer = new DtmfLocalTonePlayer();
         CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
                 context,
                 this,
@@ -221,25 +217,26 @@
                         wiredHeadsetManager,
                         mDockManager);
 
-        mCallAudioManager = new CallAudioManager(context, mLock, this, callAudioRouteStateMachine);
-
-        InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager,
+        InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
                 callAudioRoutePeripheralAdapter, lock);
 
+        SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
         RingtoneFactory ringtoneFactory = new RingtoneFactory(context);
         SystemVibrator systemVibrator = new SystemVibrator(context);
         AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
-        SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
-        mRinger = new Ringer(
-                mCallAudioManager, this, playerFactory, context, systemSettingsUtil,
-                asyncRingtonePlayer, ringtoneFactory, systemVibrator);
+        mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
+                ringtoneFactory, systemVibrator);
+
+        mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
+                this, new CallAudioModeStateMachine(context), playerFactory,
+                mRinger, new RingbackPlayer(playerFactory), mDtmfLocalTonePlayer);
+
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
         mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
         mCallLogManager = new CallLogManager(context, phoneAccountRegistrar);
-        mInCallController = new InCallController(context, mLock, this);
-        mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
+        mInCallController = new InCallController(context, mLock, this, systemStateProvider);
         mConnectionServiceRepository =
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
@@ -249,12 +246,8 @@
         mListeners.add(mCallLogManager);
         mListeners.add(mPhoneStateBroadcaster);
         mListeners.add(mInCallController);
-        mListeners.add(mRinger);
-        mListeners.add(new RingbackPlayer(this, playerFactory));
-        mListeners.add(new InCallToneMonitor(playerFactory, this));
         mListeners.add(mCallAudioManager);
         mListeners.add(missedCallNotifier);
-        mListeners.add(mDtmfLocalTonePlayer);
         mListeners.add(mHeadsetMediaButton);
         mListeners.add(mProximitySensorManager);
 
@@ -308,7 +301,7 @@
     public void onSuccessfulIncomingCall(Call incomingCall, boolean shouldSendToVoicemail) {
         Log.d(this, "onSuccessfulIncomingCall");
 
-        // Only set the incoming call as ringing if it isn't already disconnected.  It is possible
+        // Only set the incoming call as ringing if it isn't already disconnected. It is possible
         // that the connection service disconnected the call before it was even added to Telecom, in
         // which case it makes no sense to set it back to a ringing state.
         if (incomingCall.getState() != CallState.DISCONNECTED &&
@@ -503,11 +496,15 @@
 
     @VisibleForTesting
     public Call getForegroundCall() {
-        return mForegroundCall;
+        if (mCallAudioManager == null) {
+            // Happens when getForegroundCall is called before full initialization.
+            return null;
+        }
+        return mCallAudioManager.getForegroundCall();
     }
 
-    Ringer getRinger() {
-        return mRinger;
+    CallAudioManager getCallAudioManager() {
+        return mCallAudioManager;
     }
 
     InCallController getInCallController() {
@@ -594,8 +591,8 @@
         );
 
         call.initAnalytics();
-        if (mForegroundCall != null) {
-            mForegroundCall.getAnalytics().setCallIsInterrupted(true);
+        if (getForegroundCall() != null) {
+            getForegroundCall().getAnalytics().setCallIsInterrupted(true);
             call.getAnalytics().setCallIsAdditional(true);
         }
 
@@ -708,8 +705,8 @@
                         initiatingUser);
         Log.v(this, "startOutgoingCall found accounts = " + accounts);
 
-        if (mForegroundCall != null) {
-            Call ongoingCall = mForegroundCall;
+        if (getForegroundCall() != null) {
+            Call ongoingCall = getForegroundCall();
             // If there is an ongoing call, use the same phone account to place this new call.
             // If the ongoing call is a conference call, we fetch the phone account from the
             // child calls because we don't have targetPhoneAccount set on Conference calls.
@@ -884,18 +881,19 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Request to answer a non-existent call %s", call);
         } else {
+            Call foregroundCall = getForegroundCall();
             // If the foreground call is not the ringing call and it is currently isActive() or
             // STATE_DIALING, put it on hold before answering the call.
-            if (mForegroundCall != null && mForegroundCall != call &&
-                    (mForegroundCall.isActive() ||
-                     mForegroundCall.getState() == CallState.DIALING)) {
-                if (0 == (mForegroundCall.getConnectionCapabilities()
+            if (foregroundCall != null && foregroundCall != call &&
+                    (foregroundCall.isActive() ||
+                     foregroundCall.getState() == CallState.DIALING)) {
+                if (0 == (foregroundCall.getConnectionCapabilities()
                         & Connection.CAPABILITY_HOLD)) {
                     // This call does not support hold.  If it is from a different connection
                     // service, then disconnect it, otherwise allow the connection service to
                     // figure out the right states.
-                    if (mForegroundCall.getConnectionService() != call.getConnectionService()) {
-                        mForegroundCall.disconnect();
+                    if (foregroundCall.getConnectionService() != call.getConnectionService()) {
+                        foregroundCall.disconnect();
                     }
                 } else {
                     Call heldCall = getHeldCall();
@@ -906,8 +904,8 @@
                     }
 
                     Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
-                            mForegroundCall, call);
-                    mForegroundCall.hold();
+                            foregroundCall, call);
+                    foregroundCall.hold();
                 }
                 // TODO: Wait until we get confirmation of the active call being
                 // on-hold before answering the new call.
@@ -1188,8 +1186,9 @@
         removeCall(call);
         if (mLocallyDisconnectingCalls.contains(call)) {
             mLocallyDisconnectingCalls.remove(call);
-            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
-                mForegroundCall.unhold();
+            Call foregroundCall = getForegroundCall();
+            if (foregroundCall != null && foregroundCall.getState() == CallState.ON_HOLD) {
+                foregroundCall.unhold();
             }
         }
     }
@@ -1336,8 +1335,9 @@
     Call getFirstCallWithState(Call callToSkip, int... states) {
         for (int currentState : states) {
             // check the foreground first
-            if (mForegroundCall != null && mForegroundCall.getState() == currentState) {
-                return mForegroundCall;
+            Call foregroundCall = getForegroundCall();
+            if (foregroundCall != null && foregroundCall.getState() == currentState) {
+                return foregroundCall;
             }
 
             for (Call call : mCalls) {
@@ -1536,53 +1536,6 @@
         }
     }
 
-    /**
-     * Checks which call should be visible to the user and have audio focus.
-     */
-    private void updateForegroundCall() {
-        Trace.beginSection("updateForegroundCall");
-        Call newForegroundCall = null;
-        for (Call call : mCalls) {
-            // TODO: Foreground-ness needs to be explicitly set. No call, regardless
-            // of its state will be foreground by default and instead the connection 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.
-
-            // Only top-level calls can be in foreground
-            if (call.getParentCall() != null) {
-                continue;
-            }
-
-            // Active calls have priority.
-            if (call.isActive()) {
-                newForegroundCall = call;
-                break;
-            }
-
-            if (call.isAlive() || call.getState() == CallState.RINGING) {
-                newForegroundCall = call;
-                // 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;
-
-            for (CallsManagerListener listener : mListeners) {
-                if (Log.SYSTRACE_DEBUG) {
-                    Trace.beginSection(listener.getClass().toString() + " updateForegroundCall");
-                }
-                listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
-                if (Log.SYSTRACE_DEBUG) {
-                    Trace.endSection();
-                }
-            }
-        }
-        Trace.endSection();
-    }
-
     private void updateCanAddCall() {
         boolean newCanAddCall = canAddCall();
         if (newCanAddCall != mCanAddCall) {
@@ -1600,7 +1553,6 @@
     }
 
     private void updateCallsManagerState() {
-        updateForegroundCall();
         updateCanAddCall();
     }
 
@@ -1885,7 +1837,6 @@
             }
             pw.decreaseIndent();
         }
-        pw.println("mForegroundCall: " + (mForegroundCall == null ? "none" : mForegroundCall));
 
         if (mCallAudioManager != null) {
             pw.println("mCallAudioManager:");
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 58085a0..50716d5 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -52,10 +52,6 @@
     }
 
     @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-    }
-
-    @Override
     public void onCallAudioStateChanged(CallAudioState oldAudioState,
             CallAudioState newAudioState) {
     }
diff --git a/src/com/android/server/telecom/DockManager.java b/src/com/android/server/telecom/DockManager.java
index 1048e2b..27ffd28 100644
--- a/src/com/android/server/telecom/DockManager.java
+++ b/src/com/android/server/telecom/DockManager.java
@@ -40,10 +40,15 @@
     private class DockBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
-                int dockState = intent.getIntExtra(
-                        Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
-                onDockChanged(dockState);
+            Log.startSession("DM.oR");
+            try {
+                if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+                    int dockState = intent.getIntExtra(
+                            Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+                    onDockChanged(dockState);
+                }
+            } finally {
+                Log.endSession();
             }
         }
     }
diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index a0d2862..20c9dd8 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -34,16 +34,13 @@
  * class employs a concept of a call "session" that starts and stops when the foreground call
  * changes.
  */
-class DtmfLocalTonePlayer extends CallsManagerListenerBase {
+class DtmfLocalTonePlayer {
     /** Generator used to actually play the tone. */
     private ToneGenerator mToneGenerator;
 
     /** The current call associated with an existing dtmf session. */
     private Call mCall;
 
-    /** The context. */
-    private final Context mContext;
-
     /**
      * Message codes to be used for creating and deleting ToneGenerator object in the tonegenerator
      * thread.
@@ -54,13 +51,8 @@
     /** Handler running on the tonegenerator thread. */
     private Handler mHandler;
 
+    public DtmfLocalTonePlayer() { }
 
-    public DtmfLocalTonePlayer(Context context) {
-        mContext = context;
-    }
-
-    /** {@inheritDoc} */
-    @Override
     public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
         endDtmfSession(oldForegroundCall);
         startDtmfSession(newForegroundCall);
@@ -219,7 +211,7 @@
         };
     }
 
-    private static final int getMappedTone(char digit) {
+    private static int getMappedTone(char digit) {
         if (digit >= '0' && digit <= '9') {
             return ToneGenerator.TONE_DTMF_0 + digit - '0';
         } else if (digit == '#') {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index df19474..26ff625 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -47,10 +47,12 @@
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.telecom.IInCallService;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.SystemStateProvider.SystemStateListener;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -136,6 +138,19 @@
         }
     };
 
+    private final SystemStateListener mSystemStateListener = new SystemStateListener() {
+        @Override
+        public void onCarModeChanged(boolean isCarMode) {
+            // Do something when the car mode changes.
+        }
+    };
+
+    private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
+    private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
+    private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
+    private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
+    private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
+
     /**
      * Maintains a binding connection to the in-call app(s).
      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
@@ -161,17 +176,21 @@
     private final Context mContext;
     private final TelecomSystem.SyncRoot mLock;
     private final CallsManager mCallsManager;
+    private final SystemStateProvider mSystemStateProvider;
 
-    public InCallController(
-            Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
+    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
+            SystemStateProvider systemStateProvider) {
         mContext = context;
         mLock = lock;
         mCallsManager = callsManager;
-        Resources resources = mContext.getResources();
+        mSystemStateProvider = systemStateProvider;
 
+        Resources resources = mContext.getResources();
         mSystemInCallComponentName = new ComponentName(
                 resources.getString(R.string.ui_default_package),
                 resources.getString(R.string.incall_default_class));
+
+        mSystemStateProvider.addListener(mSystemStateListener);
     }
 
     @Override
@@ -330,82 +349,80 @@
      * @param call The newly added call that triggered the binding to the in-call services.
      */
     private void bindToServices(Call call) {
+        ComponentName inCallUIService = null;
+        ComponentName carModeInCallUIService = null;
+        List<ComponentName> nonUIInCallServices = new LinkedList<>();
+
+        // Loop through all the InCallService implementations that exist in the devices;
         PackageManager packageManager = mContext.getPackageManager();
         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
-
-        List<ComponentName> inCallControlServices = new ArrayList<>();
-        ComponentName inCallUIService = null;
-
         for (ResolveInfo entry :
                 packageManager.queryIntentServices(serviceIntent, PackageManager.GET_META_DATA)) {
             ServiceInfo serviceInfo = entry.serviceInfo;
             if (serviceInfo != null) {
-                boolean hasServiceBindPermission = serviceInfo.permission != null &&
-                        serviceInfo.permission.equals(
-                                Manifest.permission.BIND_INCALL_SERVICE);
-                if (!hasServiceBindPermission) {
-                    Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " +
-                            serviceInfo.packageName);
-                    continue;
-                }
+                ComponentName componentName =
+                        new ComponentName(serviceInfo.packageName, serviceInfo.name);
 
-                boolean hasControlInCallPermission = packageManager.checkPermission(
-                        Manifest.permission.CONTROL_INCALL_EXPERIENCE,
-                        serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
-                boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
-                        DefaultDialerManager.getDefaultDialerApplication(mContext));
-                if (!hasControlInCallPermission && !isDefaultDialerPackage) {
-                    Log.w(this, "Service does not have CONTROL_INCALL_EXPERIENCE permission: %s"
-                            + " and is not system or default dialer.", serviceInfo.packageName);
-                    continue;
-                }
+                switch (getInCallServiceType(entry.serviceInfo, packageManager)) {
+                    case IN_CALL_SERVICE_TYPE_DIALER_UI:
+                        if (inCallUIService == null ||
+                                inCallUIService.compareTo(componentName) > 0) {
+                            inCallUIService = componentName;
+                        }
+                        break;
 
-                boolean isUIService = serviceInfo.metaData != null &&
-                        serviceInfo.metaData.getBoolean(
-                                TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
-                ComponentName componentName = new ComponentName(serviceInfo.packageName,
-                        serviceInfo.name);
-                if (isUIService) {
-                    // For the main UI service, we always prefer the default dialer.
-                    if (isDefaultDialerPackage) {
-                        inCallUIService = componentName;
-                        Log.i(this, "Found default-dialer's In-Call UI: %s", componentName);
-                    }
-                } else {
-                    // for non-UI services that have passed our checks, add them to the list of
-                    // service to bind to.
-                    inCallControlServices.add(componentName);
-                }
+                    case IN_CALL_SERVICE_TYPE_SYSTEM_UI:
+                        // skip, will be added manually
+                        break;
 
+                    case IN_CALL_SERVICE_TYPE_CAR_MODE_UI:
+                        if (carModeInCallUIService == null ||
+                                carModeInCallUIService.compareTo(componentName) > 0) {
+                            carModeInCallUIService = componentName;
+                        }
+                        break;
+
+                    case IN_CALL_SERVICE_TYPE_NON_UI:
+                        nonUIInCallServices.add(componentName);
+                        break;
+
+                    case IN_CALL_SERVICE_TYPE_INVALID:
+                        break;
+
+                    default:
+                        Log.w(this, "unexpected in-call service type");
+                        break;
+                }
             }
         }
 
-        // Attempt to bind to the default-dialer InCallService first.
-        if (inCallUIService != null) {
-            // skip default dialer if we have an emergency call or if it failed binding.
-            if (mCallsManager.hasEmergencyCall()) {
-                Log.i(this, "Skipping default-dialer because of emergency call");
-                inCallUIService = null;
-            } else if (!bindToInCallService(inCallUIService, call, "def-dialer")) {
-                Log.event(call, Log.Events.ERROR_LOG,
-                        "InCallService UI failed binding: " + inCallUIService);
-                inCallUIService = null;
-            }
-        }
+        Log.i(this, "Car mode InCallService: %s", carModeInCallUIService);
+        Log.i(this, "Dialer InCallService: %s", inCallUIService);
 
-        if (inCallUIService == null) {
-            // We failed to connect to the default-dialer service, or none was provided. Switch to
-            // the system built-in InCallService UI.
-            inCallUIService = mSystemInCallComponentName;
-            if (!bindToInCallService(inCallUIService, call, "system")) {
-                Log.event(call, Log.Events.ERROR_LOG,
-                        "InCallService system UI failed binding: " + inCallUIService);
-            }
+        // Adding the in-call services in order:
+        // (1) The carmode in-call if carmode is on.
+        // (2) The default-dialer in-call if not an emergency call
+        // (3) The system-provided in-call
+        List<ComponentName> orderedInCallUIServices = new LinkedList<>();
+        if (shouldUseCarModeUI() && carModeInCallUIService != null) {
+            orderedInCallUIServices.add(carModeInCallUIService);
+        }
+        if (!mCallsManager.hasEmergencyCall() && inCallUIService != null) {
+            orderedInCallUIServices.add(inCallUIService);
+        }
+        orderedInCallUIServices.add(mSystemInCallComponentName);
+
+        // TODO: Need to implement the fall-back logic in case the main UI in-call service rejects
+        // the binding request.
+        ComponentName inCallUIServiceToBind = orderedInCallUIServices.get(0);
+        if (!bindToInCallService(inCallUIServiceToBind, call, "ui")) {
+            Log.event(call, Log.Events.ERROR_LOG,
+                    "InCallService system UI failed binding: " + inCallUIService);
         }
         mInCallUIComponentName = inCallUIService;
 
         // Bind to the control InCallServices
-        for (ComponentName componentName : inCallControlServices) {
+        for (ComponentName componentName : nonUIInCallServices) {
             bindToInCallService(componentName, call, "control");
         }
     }
@@ -445,6 +462,68 @@
         return false;
     }
 
+    private boolean shouldUseCarModeUI() {
+        return mSystemStateProvider.isCarMode();
+    }
+
+    /**
+     * Returns the type of InCallService described by the specified serviceInfo.
+     */
+    private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
+        // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
+        // enforces that only Telecom can bind to it.
+        boolean hasServiceBindPermission = serviceInfo.permission != null &&
+                serviceInfo.permission.equals(
+                        Manifest.permission.BIND_INCALL_SERVICE);
+        if (!hasServiceBindPermission) {
+            Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
+                    serviceInfo.packageName);
+            return IN_CALL_SERVICE_TYPE_INVALID;
+        }
+
+        if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
+                mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
+            return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
+        }
+
+        // Check to see if the service is a car-mode UI type by checking that it has the
+        // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
+        // car-mode UI metadata.
+        boolean hasControlInCallPermission = packageManager.checkPermission(
+                Manifest.permission.CONTROL_INCALL_EXPERIENCE,
+                serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
+        boolean isCarModeUIService = serviceInfo.metaData != null &&
+                serviceInfo.metaData.getBoolean(
+                        TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
+                hasControlInCallPermission;
+        if (isCarModeUIService) {
+            return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+        }
+
+
+        // Check to see that it is the default dialer package
+        boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
+                DefaultDialerManager.getDefaultDialerApplication(mContext));
+        boolean isUIService = serviceInfo.metaData != null &&
+                serviceInfo.metaData.getBoolean(
+                        TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
+        if (isDefaultDialerPackage && isUIService) {
+            return IN_CALL_SERVICE_TYPE_DIALER_UI;
+        }
+
+        // Also allow any in-call service that has the control-experience permission (to ensure
+        // that it is a system app) and doesn't claim to show any UI.
+        if (hasControlInCallPermission && !isUIService) {
+            return IN_CALL_SERVICE_TYPE_NON_UI;
+        }
+
+        // Anything else that remains, we will not bind to.
+        Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
+                serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
+                isCarModeUIService, isUIService);
+        return IN_CALL_SERVICE_TYPE_INVALID;
+    }
+
     private void adjustServiceBindingsForEmergency() {
         if (!Objects.equals(mInCallUIComponentName, mSystemInCallComponentName)) {
             // The connected UI is not the system UI, so lets check if we should switch them
diff --git a/src/com/android/server/telecom/InCallToneMonitor.java b/src/com/android/server/telecom/InCallToneMonitor.java
deleted file mode 100644
index afe0f06..0000000
--- a/src/com/android/server/telecom/InCallToneMonitor.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2014, 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.server.telecom;
-
-import android.media.ToneGenerator;
-import android.telecom.Connection;
-import android.telecom.VideoProfile;
-
-import java.util.Collection;
-
-/**
- * Monitors events from CallsManager and plays in-call tones for events which require them, such as
- * different type of call disconnections (busy tone, congestion tone, etc).
- */
-public final class InCallToneMonitor extends CallsManagerListenerBase {
-    private final InCallTonePlayer.Factory mPlayerFactory;
-
-    private final CallsManager mCallsManager;
-
-    InCallToneMonitor(InCallTonePlayer.Factory playerFactory, CallsManager callsManager) {
-        mPlayerFactory = playerFactory;
-        mCallsManager = callsManager;
-    }
-
-    @Override
-    public void onCallStateChanged(Call call, int oldState, int newState) {
-        if (mCallsManager.getForegroundCall() != call) {
-            // We only play tones for foreground calls.
-            return;
-        }
-
-        if (newState == CallState.DISCONNECTED && call.getDisconnectCause() != null) {
-            int toneToPlay = InCallTonePlayer.TONE_INVALID;
-
-            Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
-
-            switch(call.getDisconnectCause().getTone()) {
-                case ToneGenerator.TONE_SUP_BUSY:
-                    toneToPlay = InCallTonePlayer.TONE_BUSY;
-                    break;
-                case ToneGenerator.TONE_SUP_CONGESTION:
-                    toneToPlay = InCallTonePlayer.TONE_CONGESTION;
-                    break;
-                case ToneGenerator.TONE_CDMA_REORDER:
-                    toneToPlay = InCallTonePlayer.TONE_REORDER;
-                    break;
-                case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
-                    toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
-                    break;
-                case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
-                    toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
-                    break;
-                case ToneGenerator.TONE_SUP_ERROR:
-                    toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
-                    break;
-                case ToneGenerator.TONE_PROP_PROMPT:
-                    toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
-                    break;
-            }
-
-            Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
-
-            if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
-                mPlayerFactory.createPlayer(toneToPlay).startTone();
-            }
-        }
-    }
-
-    /**
-     * Handles requests received via the {@link VideoProviderProxy} requesting a change in the video
-     * state of the call by the peer.  If the request involves the peer turning their camera on,
-     * the call waiting tone is played to inform the user of the incoming request.
-     *
-     * @param call The call.
-     * @param videoProfile The requested video profile.
-     */
-    @Override
-    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
-        if (videoProfile == null) {
-            return;
-        }
-
-        if (mCallsManager.getForegroundCall() != call) {
-            // We only play tones for foreground calls.
-            return;
-        }
-
-        int previousVideoState = call.getVideoState();
-        int newVideoState = videoProfile.getVideoState();
-        Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
-                .videoStateToString(newVideoState));
-
-        boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
-                VideoProfile.isReceptionEnabled(newVideoState);
-
-        if (isUpgradeRequest) {
-            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
-        }
-    }
-}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 4fa4389..77406d3 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -34,18 +34,20 @@
      * Factory used to create InCallTonePlayers. Exists to aid with testing mocks.
      */
     public static class Factory {
-        private final CallAudioManager mCallAudioManager;
+        private CallAudioManager mCallAudioManager;
         private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
         private final TelecomSystem.SyncRoot mLock;
 
-        Factory(CallAudioManager callAudioManager,
-                CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
+        Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
                 TelecomSystem.SyncRoot lock) {
-            mCallAudioManager = callAudioManager;
             mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
             mLock = lock;
         }
 
+        public void setCallAudioManager(CallAudioManager callAudioManager) {
+            mCallAudioManager = callAudioManager;
+        }
+
         public InCallTonePlayer createPlayer(int tone) {
             return new InCallTonePlayer(tone, mCallAudioManager,
                     mCallAudioRoutePeripheralAdapter, mLock);
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index 57ae24b..54329c8 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -43,26 +43,20 @@
 
     @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
-        if ((newState == CallState.DIALING || newState == CallState.ACTIVE
-                || newState == CallState.ON_HOLD) &&
-                !mCallsManager.hasRingingCall()) {
-            /*
-             * EXTRA_STATE_RINGING takes precedence over EXTRA_STATE_OFFHOOK, so if there is
-             * already a ringing call, don't broadcast EXTRA_STATE_OFFHOOK.
-             */
-            sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_OFFHOOK);
-        }
+        updateStates(call);
     }
 
     @Override
     public void onCallAdded(Call call) {
-        if (call.getState() == CallState.RINGING) {
-            sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_RINGING);
-        }
-    };
+        updateStates(call);
+    }
 
     @Override
     public void onCallRemoved(Call call) {
+        updateStates(call);
+    }
+
+    private void updateStates(Call call) {
         // Recalculate the current phone state based on the consolidated state of the remaining
         // calls in the call list.
         int callState = TelephonyManager.CALL_STATE_IDLE;
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index bb2055f..af60b68 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -1,17 +1,17 @@
 /*
- * Copyright 2014, The Android Open Source Project
+ * Copyright (C) 2014 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
+ *      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.
+ * limitations under the License
  */
 
 package com.android.server.telecom;
@@ -24,9 +24,7 @@
  * able to turn off and on as the user switches between calls. This is why it is implemented as its
  * own class.
  */
-class RingbackPlayer extends CallsManagerListenerBase {
-
-    private final CallsManager mCallsManager;
+class RingbackPlayer {
 
     private final InCallTonePlayer.Factory mPlayerFactory;
 
@@ -40,52 +38,16 @@
      */
     private InCallTonePlayer mTonePlayer;
 
-    RingbackPlayer(CallsManager callsManager, InCallTonePlayer.Factory playerFactory) {
-        mCallsManager = callsManager;
+    RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
         mPlayerFactory = playerFactory;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-        if (oldForegroundCall != null) {
-            stopRingbackForCall(oldForegroundCall);
-        }
-
-        if (shouldStartRinging(newForegroundCall)) {
-            startRingbackForCall(newForegroundCall);
-        }
-    }
-
-    @Override
-    public void onConnectionServiceChanged(
-            Call call,
-            ConnectionServiceWrapper oldService,
-            ConnectionServiceWrapper newService) {
-
-        // Treat as ending or begining dialing based on the state transition.
-        if (shouldStartRinging(call)) {
-            startRingbackForCall(call);
-        } else if (newService == null) {
-            stopRingbackForCall(call);
-        }
-    }
-
-    @Override
-    public void onRingbackRequested(Call call, boolean ignored) {
-        if (shouldStartRinging(call)) {
-            startRingbackForCall(call);
-        } else {
-            stopRingbackForCall(call);
-        }
-    }
-
     /**
      * Starts ringback for the specified dialing call as needed.
      *
      * @param call The call for which to ringback.
      */
-    private void startRingbackForCall(Call call) {
+    public void startRingbackForCall(Call call) {
         Preconditions.checkState(call.getState() == CallState.DIALING);
 
         if (mCall == call) {
@@ -112,7 +74,7 @@
      *
      * @param call The call for which to stop ringback.
      */
-    private void stopRingbackForCall(Call call) {
+    public void stopRingbackForCall(Call call) {
         if (mCall == call) {
             // The foreground call is no longer dialing or is no longer the foreground call. In
             // either case, stop the ringback tone.
@@ -127,11 +89,4 @@
             }
         }
     }
-
-    private boolean shouldStartRinging(Call call) {
-        return call != null
-                && mCallsManager.getForegroundCall() == call
-                && call.getState() == CallState.DIALING
-                && call.isRingbackRequested();
-    }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index da22e98..0b96e99 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,7 +11,7 @@
  * 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.
+ * limitations under the License
  */
 
 package com.android.server.telecom;
@@ -27,25 +27,17 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.LinkedList;
-import java.util.List;
-
 /**
  * Controls the ringtone player.
- * TODO: Turn this into a proper state machine: Ringing, CallWaiting, Stopped.
  */
 @VisibleForTesting
-public final class Ringer extends CallsManagerListenerBase {
+public final class Ringer {
     private static final long[] VIBRATION_PATTERN = new long[] {
         0, // No delay before starting
         1000, // How long to vibrate
         1000, // How long to wait before vibrating again
     };
 
-    private static final int STATE_RINGING = 1;
-    private static final int STATE_CALL_WAITING = 2;
-    private static final int STATE_STOPPED = 3;
-
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -58,21 +50,23 @@
      * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
      * calls and explicit ordering is useful for maintaining the proper state of the ringer.
      */
-    private final List<Call> mRingingCalls = new LinkedList<>();
 
     private final SystemSettingsUtil mSystemSettingsUtil;
-    private final CallAudioManager mCallAudioManager;
-    private final CallsManager mCallsManager;
     private final InCallTonePlayer.Factory mPlayerFactory;
     private final AsyncRingtonePlayer mRingtonePlayer;
     private final Context mContext;
     private final Vibrator mVibrator;
 
-    private int mState = STATE_STOPPED;
     private InCallTonePlayer mCallWaitingPlayer;
     private RingtoneFactory mRingtoneFactory;
 
     /**
+     * Call objects that are ringing or call-waiting. These are used only for logging purposes.
+     */
+    private Call mRingingCall;
+    private Call mCallWaitingCall;
+
+    /**
      * Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
      */
     private boolean mIsVibrating = false;
@@ -80,8 +74,6 @@
     /** Initializes the Ringer. */
     @VisibleForTesting
     public Ringer(
-            CallAudioManager callAudioManager,
-            CallsManager callsManager,
             InCallTonePlayer.Factory playerFactory,
             Context context,
             SystemSettingsUtil systemSettingsUtil,
@@ -90,8 +82,6 @@
             Vibrator vibrator) {
 
         mSystemSettingsUtil = systemSettingsUtil;
-        mCallAudioManager = callAudioManager;
-        mCallsManager = callsManager;
         mPlayerFactory = playerFactory;
         mContext = context;
         // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
@@ -101,156 +91,81 @@
         mRingtoneFactory = ringtoneFactory;
     }
 
-    @Override
-    public void onCallAdded(final Call call) {
-        if (call.isIncoming() && call.getState() == CallState.RINGING) {
-            if (mRingingCalls.contains(call)) {
-                Log.wtf(this, "New ringing call is already in list of unanswered calls");
-            }
-            mRingingCalls.add(call);
-            updateRinging(call);
-        }
-    }
-
-    @Override
-    public void onCallRemoved(Call call) {
-        removeFromUnansweredCall(call);
-    }
-
-    @Override
-    public void onCallStateChanged(Call call, int oldState, int newState) {
-        if (newState != CallState.RINGING) {
-            removeFromUnansweredCall(call);
-        }
-    }
-
-    @Override
-    public void onIncomingCallAnswered(Call call) {
-        onRespondedToIncomingCall(call);
-    }
-
-    @Override
-    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
-        onRespondedToIncomingCall(call);
-    }
-
-    @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-        Call ringingCall = null;
-        if (mRingingCalls.contains(newForegroundCall)) {
-            ringingCall = newForegroundCall;
-        } else if (mRingingCalls.contains(oldForegroundCall)) {
-            ringingCall = oldForegroundCall;
-        }
-        if (ringingCall != null) {
-            updateRinging(ringingCall);
-        }
-    }
-
-    /**
-     * Silences the ringer for any actively ringing calls.
-     */
-    void silence() {
-        for (Call call : mRingingCalls) {
-            call.silence();
-        }
-
-        // Remove all calls from the "ringing" set and then update the ringer.
-        mRingingCalls.clear();
-        updateRinging(null);
-    }
-
-    private void onRespondedToIncomingCall(Call call) {
-        // Only stop the ringer if this call is the top-most incoming call.
-        if (getTopMostUnansweredCall() == call) {
-            removeFromUnansweredCall(call);
-        }
-    }
-
-    private Call getTopMostUnansweredCall() {
-        return mRingingCalls.isEmpty() ? null : mRingingCalls.get(0);
-    }
-
-    /**
-     * Removes the specified call from the list of unanswered incoming calls and updates the ringer
-     * based on the new state of {@link #mRingingCalls}. Safe to call with a call that is not
-     * present in the list of incoming calls.
-     */
-    private void removeFromUnansweredCall(Call call) {
-        mRingingCalls.remove(call);
-        updateRinging(call);
-    }
-
-    private void updateRinging(Call call) {
-        if (mRingingCalls.isEmpty()) {
-            stopRinging(call, "No more ringing calls found");
-            stopCallWaiting(call);
-        } else {
-            startRingingOrCallWaiting(call);
-        }
-    }
-
-    private void startRingingOrCallWaiting(Call call) {
-        Call foregroundCall = mCallsManager.getForegroundCall();
-        Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
-
+    public void startRinging(Call foregroundCall) {
         if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
             return;
         }
 
-        if (mRingingCalls.contains(foregroundCall)) {
-            // The foreground call is one of incoming calls so play the ringer out loud.
-            stopCallWaiting(call);
+        stopCallWaiting();
 
-            if (!shouldRingForContact(foregroundCall.getContactUri())) {
-                return;
+        if (!shouldRingForContact(foregroundCall.getContactUri())) {
+            return;
+        }
+
+        AudioManager audioManager =
+                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+            mRingingCall = foregroundCall;
+            Log.event(foregroundCall, Log.Events.START_RINGER);
+            // Because we wait until a contact info query to complete before processing a
+            // call (for the purposes of direct-to-voicemail), the information about custom
+            // ringtones should be available by the time this code executes. We can safely
+            // request the custom ringtone from the call and expect it to be current.
+            mRingtonePlayer.play(
+                    mRingtoneFactory.getRingtone(foregroundCall.getRingtone()));
+        } else {
+            Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
+        }
+
+        if (shouldVibrate(mContext) && !mIsVibrating) {
+            mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
+                    VIBRATION_ATTRIBUTES);
+            mIsVibrating = true;
+        }
+    }
+
+    public void startCallWaiting(Call call) {
+        if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
+            return;
+        }
+
+        Log.v(this, "Playing call-waiting tone.");
+
+        stopRinging();
+
+        if (mCallWaitingPlayer == null) {
+            Log.event(call, Log.Events.START_CALL_WAITING_TONE);
+            mCallWaitingCall = call;
+            mCallWaitingPlayer =
+                    mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+            mCallWaitingPlayer.startTone();
+        }
+    }
+
+    public void stopRinging() {
+        if (mRingingCall != null) {
+            Log.event(mRingingCall, Log.Events.STOP_RINGER);
+            mRingingCall = null;
+        }
+
+        mRingtonePlayer.stop();
+
+        if (mIsVibrating) {
+            mVibrator.cancel();
+            mIsVibrating = false;
+        }
+    }
+
+    public void stopCallWaiting() {
+        Log.v(this, "stop call waiting.");
+        if (mCallWaitingPlayer != null) {
+            if (mCallWaitingCall != null) {
+                Log.event(mCallWaitingCall, Log.Events.STOP_CALL_WAITING_TONE);
+                mCallWaitingCall = null;
             }
 
-            AudioManager audioManager =
-                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
-                if (mState != STATE_RINGING) {
-                    Log.event(call, Log.Events.START_RINGER);
-                    mState = STATE_RINGING;
-                }
-                mCallAudioManager.setIsRinging(call, true);
-
-                // Because we wait until a contact info query to complete before processing a
-                // call (for the purposes of direct-to-voicemail), the information about custom
-                // ringtones should be available by the time this code executes. We can safely
-                // request the custom ringtone from the call and expect it to be current.
-                mRingtonePlayer.play(
-                        mRingtoneFactory.getRingtone(foregroundCall.getRingtone()));
-            } else {
-                Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
-            }
-
-            if (shouldVibrate(mContext) && !mIsVibrating) {
-                mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
-                        VIBRATION_ATTRIBUTES);
-                mIsVibrating = true;
-            }
-        } else if (foregroundCall != null) {
-            // The first incoming call added to Telecom is not a foreground call at this point
-            // in time. If the current foreground call is null at point, don't play call-waiting
-            // as the call will eventually be promoted to the foreground call and play the
-            // ring tone.
-            Log.v(this, "Playing call-waiting tone.");
-
-            // All incoming calls are in background so play call waiting.
-            stopRinging(call, "Stop for call-waiting");
-
-
-            if (mState != STATE_CALL_WAITING) {
-                Log.event(call, Log.Events.START_CALL_WAITING_TONE);
-                mState = STATE_CALL_WAITING;
-            }
-
-            if (mCallWaitingPlayer == null) {
-                mCallWaitingPlayer =
-                        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
-                mCallWaitingPlayer.startTone();
-            }
+            mCallWaitingPlayer.stopTone();
+            mCallWaitingPlayer = null;
         }
     }
 
@@ -264,37 +179,6 @@
         return manager.matchesCallFilter(extras);
     }
 
-    private void stopRinging(Call call, String reasonTag) {
-        if (mState == STATE_RINGING) {
-            Log.event(call, Log.Events.STOP_RINGER, reasonTag);
-            mState = STATE_STOPPED;
-        }
-
-        mRingtonePlayer.stop();
-
-        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(call, false);
-    }
-
-    private void stopCallWaiting(Call call) {
-        Log.v(this, "stop call waiting.");
-        if (mCallWaitingPlayer != null) {
-            mCallWaitingPlayer.stopTone();
-            mCallWaitingPlayer = null;
-        }
-
-        if (mState == STATE_CALL_WAITING) {
-            Log.event(call, Log.Events.STOP_CALL_WAITING_TONE);
-            mState = STATE_STOPPED;
-        }
-    }
-
     private boolean shouldVibrate(Context context) {
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         int ringerMode = audioManager.getRingerModeInternal();
diff --git a/src/com/android/server/telecom/Session.java b/src/com/android/server/telecom/Session.java
index 25a2189..51ef0fa 100644
--- a/src/com/android/server/telecom/Session.java
+++ b/src/com/android/server/telecom/Session.java
@@ -162,10 +162,15 @@
 
     // Builds full session id recursively
     private String getFullSessionId() {
-        if(mParentSession == null) {
+        // Cache mParentSession locally to prevent a concurrency problem where
+        // Log.endParentSessions() is called while a logging statement is running (Log.i, for
+        // example) and setting mParentSession to null in a different thread after the null check
+        // occurred.
+        Session parentSession = mParentSession;
+        if(parentSession == null) {
             return mSessionId;
         } else {
-            return mParentSession.getFullSessionId() + "_" + mSessionId;
+            return parentSession.getFullSessionId() + "_" + mSessionId;
         }
     }
 
diff --git a/src/com/android/server/telecom/SystemStateProvider.java b/src/com/android/server/telecom/SystemStateProvider.java
new file mode 100644
index 0000000..0b636cf
--- /dev/null
+++ b/src/com/android/server/telecom/SystemStateProvider.java
@@ -0,0 +1,123 @@
+/*
+ * 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.server.telecom;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Provides various system states to the rest of the telecom codebase. So far, that's only car-mode.
+ */
+public class SystemStateProvider {
+
+    public static interface SystemStateListener {
+        public void onCarModeChanged(boolean isCarMode);
+    }
+
+    private final Context mContext;
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.startSession("SSP.oR");
+            try {
+                String action = intent.getAction();
+                if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
+                    onEnterCarMode();
+                } else if (UiModeManager.ACTION_EXIT_CAR_MODE.equals(action)) {
+                    onExitCarMode();
+                } else {
+                    Log.w(this, "Unexpected intent received: %s", intent.getAction());
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    };
+
+    private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
+    private boolean mIsCarMode;
+
+    public SystemStateProvider(Context context) {
+        mContext = context;
+
+        IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
+        intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+        Log.i(this, "Registering car mode receiver: %s", intentFilter);
+
+        mIsCarMode = getSystemCarMode();
+    }
+
+    public void addListener(SystemStateListener listener) {
+        if (listener != null) {
+            mListeners.add(listener);
+        }
+    }
+
+    public boolean removeListener(SystemStateListener listener) {
+        return mListeners.remove(listener);
+    }
+
+    public boolean isCarMode() {
+        return mIsCarMode;
+    }
+
+    private void onEnterCarMode() {
+        if (!mIsCarMode) {
+            Log.i(this, "Entering carmode");
+            mIsCarMode = true;
+            notifyCarMode();
+        }
+    }
+
+    private void onExitCarMode() {
+        if (mIsCarMode) {
+            Log.i(this, "Exiting carmode");
+            mIsCarMode = false;
+            notifyCarMode();
+        }
+    }
+
+    private void notifyCarMode() {
+        for (SystemStateListener listener : mListeners) {
+            listener.onCarModeChanged(mIsCarMode);
+        }
+    }
+
+    /**
+     * Checks the system for the current car mode.
+     *
+     * @return True if in car mode, false otherwise.
+     */
+    private boolean getSystemCarMode() {
+        UiModeManager uiModeManager =
+                (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+
+        if (uiModeManager != null) {
+            return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index c635086..f91e086 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -568,7 +568,7 @@
                     long token = Binder.clearCallingIdentity();
                     try {
                         Log.i(this, "Silence Ringer requested by %s", callingPackage);
-                        mCallsManager.getRinger().silence();
+                        mCallsManager.getCallAudioManager().silenceRingers();
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index d0528e8..702b59d 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -104,19 +104,29 @@
     private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-            UserHandle currentUserHandle = new UserHandle(userHandleId);
-            mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
-            mCallsManager.onUserSwitch(currentUserHandle);
+            Log.startSession("TSSwR.oR");
+            try {
+                int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                UserHandle currentUserHandle = new UserHandle(userHandleId);
+                mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+                mCallsManager.onUserSwitch(currentUserHandle);
+            } finally {
+                Log.endSession();
+            }
         }
     };
 
     private final BroadcastReceiver mUserStartingReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-            UserHandle addingUserHandle = new UserHandle(userHandleId);
-            mCallsManager.onUserStarting(addingUserHandle);
+            Log.startSession("TSStR.oR");
+            try {
+                int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                UserHandle addingUserHandle = new UserHandle(userHandleId);
+                mCallsManager.onUserStarting(addingUserHandle);
+            } finally {
+                Log.endSession();
+            }
         }
     };
 
@@ -156,6 +166,7 @@
                 });
         BluetoothManager bluetoothManager = new BluetoothManager(mContext);
         WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
+        SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
 
         mMissedCallNotifier = missedCallNotifierImplFactory
                 .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar);
@@ -172,7 +183,8 @@
                 inCallWakeLockControllerFactory,
                 audioServiceFactory,
                 bluetoothManager,
-                wiredHeadsetManager);
+                wiredHeadsetManager,
+                systemStateProvider);
 
         mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
         mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java
index e0a9e9c..25284e4 100644
--- a/src/com/android/server/telecom/TtyManager.java
+++ b/src/com/android/server/telecom/TtyManager.java
@@ -112,15 +112,20 @@
     private final class TtyBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            Log.v(TtyManager.this, "onReceive, action: %s", action);
-            if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
-                int newPreferredTtyMode = intent.getIntExtra(
-                        TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
-                if (mPreferredTtyMode != newPreferredTtyMode) {
-                    mPreferredTtyMode = newPreferredTtyMode;
-                    updateCurrentTtyMode();
+            Log.startSession("TBR.oR");
+            try {
+                String action = intent.getAction();
+                Log.v(TtyManager.this, "onReceive, action: %s", action);
+                if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
+                    int newPreferredTtyMode = intent.getIntExtra(
+                            TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
+                    if (mPreferredTtyMode != newPreferredTtyMode) {
+                        mPreferredTtyMode = newPreferredTtyMode;
+                        updateCurrentTtyMode();
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
     }
diff --git a/src/com/android/server/telecom/WiredHeadsetManager.java b/src/com/android/server/telecom/WiredHeadsetManager.java
index f25e928..253dfca 100644
--- a/src/com/android/server/telecom/WiredHeadsetManager.java
+++ b/src/com/android/server/telecom/WiredHeadsetManager.java
@@ -41,11 +41,16 @@
     private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
-                boolean isPluggedIn = mAudioManager.isWiredHeadsetOn();
-                Log.v(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b",
-                        isPluggedIn);
-                onHeadsetPluggedInChanged(isPluggedIn);
+            Log.startSession("WHBR.oR");
+            try {
+                if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
+                    boolean isPluggedIn = mAudioManager.isWiredHeadsetOn();
+                    Log.v(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b",
+                            isPluggedIn);
+                    onHeadsetPluggedInChanged(isPluggedIn);
+                }
+            } finally {
+                Log.endSession();
             }
         }
     }
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 878952e..8e4ae57 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -27,12 +27,12 @@
 
 import com.android.internal.telephony.CallerInfoAsyncQuery;
 import com.android.server.telecom.BluetoothPhoneServiceImpl;
-import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
 import com.android.server.telecom.InCallWakeLockControllerFactory;
+import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.InCallWakeLockController;
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 0672b6d..e645123 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -54,6 +54,7 @@
         <service android:name="com.android.server.telecom.testapps.TestInCallServiceImpl"
                  android:process="com.android.server.telecom.testapps.TestInCallService"
                  android:permission="android.permission.BIND_INCALL_SERVICE" >
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI" android:value="true" />
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
@@ -68,6 +69,17 @@
             </intent-filter>
         </receiver>
 
+        <activity android:name="com.android.server.telecom.testapps.TestInCallUI"
+                android:process="com.android.server.telecom.testapps.TestInCallService"
+                android:label="@string/inCallUiAppLabel"
+                android:launchMode="singleInstance">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="com.android.server.telecom.testapps.TestCallActivity"
                   android:theme="@android:style/Theme.NoDisplay"
                   android:label="@string/testCallActivityLabel">
diff --git a/testapps/res/layout/call_list_item.xml b/testapps/res/layout/call_list_item.xml
new file mode 100644
index 0000000..c9f2ff7
--- /dev/null
+++ b/testapps/res/layout/call_list_item.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+    <TextView
+            android:id="@+id/phoneNumber"
+            android:layout_gravity="left"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="25dp"
+            android:text="TextView" />
+    <TextView
+            android:id="@+id/callState"
+            android:layout_gravity="left"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="25dp"
+            android:text="TextView" />
+    <TextView
+            android:id="@+id/duration"
+            android:layout_gravity="right"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="25dp"
+            android:text="TextView" />
+</LinearLayout>
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
new file mode 100644
index 0000000..6a891e7
--- /dev/null
+++ b/testapps/res/layout/incall_screen.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    <ListView
+            android:id="@+id/callListView"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:divider="#FFCC00"
+            android:dividerHeight="4px">
+    </ListView>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/end_call_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/endCallButton" />
+        <Button
+            android:id="@+id/mute_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/muteButton" />
+        <Button
+            android:id="@+id/hold_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/holdButton" >
+        </Button>
+    </LinearLayout>
+</LinearLayout>
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 43c302d..599d5cc 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -37,4 +37,12 @@
     <!-- String for button in TestDialerActivity that tries to exercise the
             TelecomManager.cancelMissedCallNotifications() functionality -->
     <string name="cancelMissedButton">Cancel missed calls</string>
+
+    <string name="endCallButton">End Call</string>
+
+    <string name="muteButton">Mute</string>
+
+    <string name="holdButton">Hold</string>
+
+    <string name="inCallUiAppLabel">Test InCall UI</string>
 </resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
new file mode 100644
index 0000000..bea0e63
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 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.server.telecom.testapps;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+public class CallListAdapter extends BaseAdapter {
+    private static final String TAG = "CallListAdapter";
+
+    private final TestCallList.Listener mListener = new TestCallList.Listener() {
+        @Override
+        public void onCallAdded(Call call) {
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onCallRemoved(Call call) {
+            notifyDataSetChanged();
+            if (mCallList.size() == 0) {
+                mCallList.removeListener(this);
+            }
+        }
+    };
+
+    private final LayoutInflater mLayoutInflater;
+    private final TestCallList mCallList;
+    private final Handler mHandler = new Handler();
+    private final Runnable mSecondsRunnable = new Runnable() {
+        @Override
+        public void run() {
+            notifyDataSetChanged();
+            if (mCallList.size() > 0) {
+                mHandler.postDelayed(this, 1000);
+            }
+        }
+    };
+
+    public CallListAdapter(Context context) {
+        mLayoutInflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mCallList = TestCallList.getInstance();
+        mCallList.addListener(mListener);
+        mHandler.postDelayed(mSecondsRunnable, 1000);
+    }
+
+
+    @Override
+    public int getCount() {
+        Log.i(TAG, "size reporting: " + mCallList.size());
+        return mCallList.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return position;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(final int position, View convertView, ViewGroup parent) {
+        Log.i(TAG, "getView: " + position);
+        if (convertView == null) {
+            convertView = mLayoutInflater.inflate(R.layout.call_list_item, parent, false);
+        }
+
+        TextView phoneNumber = (TextView) convertView.findViewById(R.id.phoneNumber);
+        TextView duration = (TextView) convertView.findViewById(R.id.duration);
+        TextView state = (TextView) convertView.findViewById(R.id.callState);
+
+        Call call = mCallList.getCall(position);
+        Uri handle = call.getDetails().getHandle();
+        phoneNumber.setText(handle == null ? "No number" : handle.getSchemeSpecificPart());
+
+        long durationMs = System.currentTimeMillis() - call.getDetails().getConnectTimeMillis();
+        duration.setText((durationMs / 1000) + " secs");
+
+        state.setText(getStateString(call));
+
+        Log.i(TAG, "Call found: " + handle.getSchemeSpecificPart() + ", " + durationMs);
+
+        return convertView;
+    }
+
+    private static String getStateString(Call call) {
+        switch (call.getState()) {
+            case Call.STATE_ACTIVE:
+                return "active";
+            case Call.STATE_CONNECTING:
+                return "connecting";
+            case Call.STATE_DIALING:
+                return "dialing";
+            case Call.STATE_DISCONNECTED:
+                return "disconnected";
+            case Call.STATE_DISCONNECTING:
+                return "disconnecting";
+            case Call.STATE_HOLDING:
+                return "on hold";
+            case Call.STATE_NEW:
+                return "new";
+            case Call.STATE_RINGING:
+                return "ringing";
+            case Call.STATE_SELECT_PHONE_ACCOUNT:
+                return "select phone account";
+            default:
+                return "unknown";
+        }
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index a16c4e2..704c83d 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -24,6 +24,8 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -31,6 +33,12 @@
  * Maintains a list of calls received via the {@link TestInCallServiceImpl}.
  */
 public class TestCallList extends Call.Listener {
+
+    public static abstract class Listener {
+        public void onCallAdded(Call call) {}
+        public void onCallRemoved(Call call) {}
+    }
+
     private static final TestCallList INSTANCE = new TestCallList();
     private static final String TAG = "TestCallList";
 
@@ -85,9 +93,10 @@
     }
 
     // The calls the call list knows about.
-    private Set<Call> mCalls = new ArraySet<Call>();
+    private List<Call> mCalls = new LinkedList<Call>();
     private Map<Call, TestVideoCallListener> mVideoCallListeners =
             new ArrayMap<Call, TestVideoCallListener>();
+    private Set<Listener> mListeners = new ArraySet<Listener>();
 
     /**
      * Singleton accessor.
@@ -96,14 +105,32 @@
         return INSTANCE;
     }
 
+    public void addListener(Listener listener) {
+        if (listener != null) {
+            mListeners.add(listener);
+        }
+    }
+
+    public boolean removeListener(Listener listener) {
+        return mListeners.remove(listener);
+    }
+
+    public Call getCall(int position) {
+        return mCalls.get(position);
+    }
+
     public void addCall(Call call) {
         if (mCalls.contains(call)) {
             Log.e(TAG, "addCall: Call already added.");
             return;
         }
-        Log.v(TAG, "addCall: " + call + " " + System.identityHashCode(this));
+        Log.i(TAG, "addCall: " + call + " " + System.identityHashCode(this));
         mCalls.add(call);
         call.addListener(this);
+
+        for (Listener l : mListeners) {
+            l.onCallAdded(call);
+        }
     }
 
     public void removeCall(Call call) {
@@ -111,9 +138,13 @@
             Log.e(TAG, "removeCall: Call cannot be removed -- doesn't exist.");
             return;
         }
-        Log.v(TAG, "removeCall: " + call);
+        Log.i(TAG, "removeCall: " + call);
         mCalls.remove(call);
         call.removeListener(this);
+
+        for (Listener l : mListeners) {
+            l.onCallRemoved(call);
+        }
     }
 
     public void clearCalls() {
@@ -126,6 +157,10 @@
         mVideoCallListeners.clear();
     }
 
+    public int size() {
+        return mCalls.size();
+    }
+
     /**
      * For any video calls tracked, sends an upgrade to video request.
      */
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
index 68bbac9..03ca3d0 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.testapps;
 
+import android.content.Context;
+import android.content.Intent;
 import android.telecom.Call;
 import android.telecom.InCallService;
 import android.telecom.Phone;
@@ -37,7 +39,12 @@
         @Override
         public void onCallAdded(Phone phone, Call call) {
             Log.i(TAG, "onCallAdded: " + call.toString());
-            TestCallList.getInstance().addCall(call);
+            TestCallList callList = TestCallList.getInstance();
+            callList.addCall(call);
+
+            if (callList.size() == 1) {
+                startInCallUI();
+            }
         }
 
         @Override
@@ -62,4 +69,11 @@
         mPhone = null;
         TestCallList.getInstance().clearCalls();
     }
+
+    private void startInCallUI() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClass(this, TestInCallUI.class);
+        startActivity(intent);
+    }
 }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
new file mode 100644
index 0000000..ce53709
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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.server.telecom.testapps;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ListView;
+
+public class TestInCallUI extends Activity {
+
+    private ListView mListView;
+    private TestCallList mCallList;
+
+    /** ${inheritDoc} */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.incall_screen);
+
+        mListView = (ListView) findViewById(R.id.callListView);
+        mListView.setAdapter(new CallListAdapter(this));
+        mListView.setVisibility(View.VISIBLE);
+
+        mCallList = TestCallList.getInstance();
+        mCallList.addListener(new TestCallList.Listener() {
+            @Override
+            public void onCallRemoved(Call call) {
+                if (mCallList.size() == 0) {
+                    Log.i("Santos", "Ending the incall UI");
+                    finish();
+                }
+            }
+        });
+
+        View endCallButton = findViewById(R.id.end_call_button);
+        View holdButton = findViewById(R.id.hold_button);
+        View muteButton = findViewById(R.id.mute_button);
+
+        endCallButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Call call = mCallList.getCall(0);
+                if (call != null) {
+                    call.disconnect();
+                }
+            }
+        });
+        holdButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Call call = mCallList.getCall(0);
+                if (call != null) {
+                    if (call.getState() == Call.STATE_HOLDING) {
+                        call.unhold();
+                    } else {
+                        call.hold();
+                    }
+                }
+            }
+        });
+        muteButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Call call = mCallList.getCall(0);
+                if (call != null) {
+                }
+            }
+        });
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 4576690..f84a545 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -47,8 +47,8 @@
         adb shell am instrument -w com.android.server.telecom.tests/android.test.InstrumentationTestRunner
 
         To run a single test case:
-        adb shell am instrument -w com.android.server.telecom.tests/android.test.InstrumentationTestRunner
-                                -e com.android.server.telecom.tests.unit.FooUnitTest
+        adb shell am instrument -w -e class com.android.server.telecom.tests.unit.FooUnitTest \
+                               com.android.server.telecom.tests/android.test.InstrumentationTestRunner
     -->
     <instrumentation android:name="android.test.InstrumentationTestRunner"
             android:targetPackage="com.android.server.telecom.tests"
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index ff69538..670959b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -23,10 +23,10 @@
 
 import com.android.server.telecom.BluetoothManager;
 import com.android.server.telecom.Call;
-import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.StatusBarNotifier;
 import com.android.server.telecom.WiredHeadsetManager;
 
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index f5ead6c..e9cf022 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -40,7 +40,9 @@
 import com.android.server.telecom.TelephonyUtil;
 
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
@@ -48,6 +50,8 @@
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.Arrays;
 
@@ -70,7 +74,7 @@
     private static final String POST_DIAL_STRING = ";12345";
     private static final String TEST_PHONE_ACCOUNT_ID= "testPhoneAccountId";
 
-    private static final int TEST_TIMEOUT_MILLIS = 100;
+    private static final int TEST_TIMEOUT_MILLIS = 200;
     private static final int CURRENT_USER_ID = 0;
     private static final int OTHER_USER_ID = 10;
     private static final int MANAGED_USER_ID = 11;
@@ -109,7 +113,15 @@
         UserInfo managedProfileUserInfo = new UserInfo(OTHER_USER_ID, "test3",
                 UserInfo.FLAG_MANAGED_PROFILE);
 
+        doAnswer(new Answer<Uri>() {
+            @Override
+            public Uri answer(InvocationOnMock invocation) throws Throwable {
+                return (Uri) invocation.getArguments()[1];
+            }
+        }).when(mContentProvider).insert(anyString(), any(Uri.class), any(ContentValues.class));
+
         when(userManager.isUserRunning(any(UserHandle.class))).thenReturn(true);
+        when(userManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
         when(userManager.hasUserRestriction(any(String.class), any(UserHandle.class)))
                 .thenReturn(false);
         when(userManager.getUsers(any(Boolean.class)))
@@ -549,8 +561,8 @@
         ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
         try {
             Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
-            verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS)).insert(any(String.class),
-                    eq(uri), captor.capture());
+            verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).atLeastOnce())
+                    .insert(any(String.class), eq(uri), captor.capture());
         } catch (android.os.RemoteException e) {
             fail("Remote exception occurred during test execution");
         }
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index b6ce48a..7ac324e 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -182,6 +182,14 @@
         }
 
         @Override
+        public String getSystemServiceName(Class<?> svcClass) {
+            if (svcClass == UserManager.class) {
+                return Context.USER_SERVICE;
+            }
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
         public int getUserId() {
             return 0;
         }
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
deleted file mode 100644
index b8616ee..0000000
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * 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.server.telecom.tests;
-
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.Ringtone;
-import android.net.Uri;
-import android.os.Vibrator;
-
-import com.android.server.telecom.AsyncRingtonePlayer;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.CallState;
-import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.SystemSettingsUtil;
-import com.android.server.telecom.InCallTonePlayer;
-import com.android.server.telecom.Ringer;
-import com.android.server.telecom.RingtoneFactory;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-public class RingerTest extends TelecomTestCase {
-
-    @Mock Ringtone mMockRingtone;
-    @Mock Vibrator mMockVibrator;
-    @Mock RingtoneFactory mMockRingtoneFactory;
-    @Mock SystemSettingsUtil mMockSystemSettings;
-    @Mock CallsManager mMockCallsManager;
-    @Mock CallAudioManager mMockCallAudioManager;
-    @Mock InCallTonePlayer.Factory mMockToneFactory;
-
-    private Ringer mRingerUnderTest;
-
-    // These tests depend on an async handler to execute play() and stop() on the mock ringtone.
-    // In order to verify these results, the test must wait an arbitrary amount of time to make sure
-    // these methods are called.
-    private static final int TEST_TIMEOUT = 100; //milliseconds
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        MockitoAnnotations.initMocks(this);
-        // Don't involve CallAudioManager logic in this unit test
-        doNothing().when(mMockCallAudioManager).setIsRinging(any(Call.class), any(boolean.class));
-        // Assume theatre mode is off for these tests
-        when(mMockSystemSettings.isTheaterModeOn(any(Context.class))).thenReturn(false);
-        // Assume the system is set to enable vibration when ringing
-        when(mMockSystemSettings.canVibrateWhenRinging(any(Context.class))).thenReturn(true);
-        when(mMockVibrator.hasVibrator()).thenReturn(true);
-        mRingerUnderTest = new Ringer(
-                mMockCallAudioManager, mMockCallsManager, mMockToneFactory,
-                mComponentContextFixture.getTestDouble().getApplicationContext(),
-                mMockSystemSettings, new AsyncRingtonePlayer(), mMockRingtoneFactory,
-                mMockVibrator);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mRingerUnderTest = null;
-        super.tearDown();
-    }
-
-    private Call createMockRingingCall() {
-        Call mockCall = mock(Call.class);
-        when(mockCall.isIncoming()).thenReturn(true);
-        when(mockCall.getState()).thenReturn(CallState.RINGING);
-        return mockCall;
-    }
-
-    public void testPlayRingtoneOnIncomingCall() throws Exception {
-        Call mockCall = createMockRingingCall();
-        when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
-        when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
-        mRingerUnderTest.onCallAdded(mockCall);
-
-        verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
-                any(AudioAttributes.class));
-        verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
-    }
-
-    public void testCallAnsweredOnIncomingCall() throws Exception {
-        Call mockCall = createMockRingingCall();
-        when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
-        when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
-        // Make sure the ringtone plays for foreground call
-        mRingerUnderTest.onCallAdded(mockCall);
-
-        verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
-        verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
-                any(AudioAttributes.class));
-
-        // Answer Call
-        mRingerUnderTest.onIncomingCallAnswered(mockCall);
-
-        verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
-        verify(mMockVibrator).cancel();
-    }
-
-    public void testCallWaitingOnBackgroundCall() throws Exception {
-        Call mockForegroundCall = mock(Call.class);
-        Call mockBackgroundCall = createMockRingingCall();
-        // Set foreground call to already answered
-        when(mMockCallsManager.getForegroundCall()).thenReturn(mockForegroundCall);
-        InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
-        when(mMockToneFactory.createPlayer(any(int.class))).thenReturn(mockInCallTonePlayer);
-
-        // Add new call waiting call
-        mRingerUnderTest.onCallAdded(mockBackgroundCall);
-
-        verify(mockInCallTonePlayer).startTone();
-    }
-
-    public void testCallWaitingOnBackgroundCallDisconnected() throws Exception {
-        Call mockForegroundCall = mock(Call.class);
-        Call mockBackgroundCall = createMockRingingCall();
-        // Set foreground call to already answered
-        when(mMockCallsManager.getForegroundCall()).thenReturn(mockForegroundCall);
-        InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
-        when(mMockToneFactory.createPlayer(any(int.class))).thenReturn(mockInCallTonePlayer);
-
-        // Add new call waiting call
-        mRingerUnderTest.onCallAdded(mockBackgroundCall);
-
-        verify(mockInCallTonePlayer).startTone();
-
-        // Reject the call waiting call
-        mRingerUnderTest.onIncomingCallRejected(mockBackgroundCall, false, "");
-
-        verify(mockInCallTonePlayer).stopTone();
-    }
-
-    public void testCallDisconnectedWhileRinging() throws Exception {
-        Call mockCall = createMockRingingCall();
-        when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
-        when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
-        // Make sure the ringtone plays for foreground call
-        mRingerUnderTest.onCallAdded(mockCall);
-
-        verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
-        verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
-                any(AudioAttributes.class));
-
-        // Call Disconnected
-        mRingerUnderTest.onCallRemoved(mockCall);
-
-        verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
-        verify(mMockVibrator).cancel();
-    }
-
-    public void testIncomingCallRejected() throws Exception {
-        Call mockCall = createMockRingingCall();
-        when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
-        when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
-
-        // Make sure the ringtone plays for foreground call
-        mRingerUnderTest.onCallAdded(mockCall);
-
-        verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
-        verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
-                any(AudioAttributes.class));
-
-        // Answer Call
-        mRingerUnderTest.onIncomingCallRejected(mockCall, false, "");
-
-        verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
-        verify(mMockVibrator).cancel();
-    }
-}
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
new file mode 100644
index 0000000..02e7ecf
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.server.telecom.tests;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+
+import com.android.server.telecom.SystemStateProvider;
+import com.android.server.telecom.SystemStateProvider.SystemStateListener;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for SystemStateProvider
+ */
+public class SystemStateProviderTest extends TelecomTestCase {
+
+    SystemStateProvider mSystemStateProvider;
+
+    @Mock Context mContext;
+    @Mock SystemStateListener mSystemStateListener;
+    @Mock UiModeManager mUiModeManager;
+    @Mock Intent mIntentEnter;
+    @Mock Intent mIntentExit;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testListeners() throws Exception {
+        SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
+
+        assertFalse(systemStateProvider.removeListener(mSystemStateListener));
+        systemStateProvider.addListener(mSystemStateListener);
+        assertTrue(systemStateProvider.removeListener(mSystemStateListener));
+        assertFalse(systemStateProvider.removeListener(mSystemStateListener));
+    }
+
+    public void testQuerySystemForCarMode_True() {
+        when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        assertTrue(new SystemStateProvider(mContext).isCarMode());
+    }
+
+    public void testQuerySystemForCarMode_False() {
+        when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL);
+        assertFalse(new SystemStateProvider(mContext).isCarMode());
+    }
+
+    public void testReceiverAndIntentFilter() {
+        ArgumentCaptor<IntentFilter> intentFilter = ArgumentCaptor.forClass(IntentFilter.class);
+        new SystemStateProvider(mContext);
+        verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
+
+        assertEquals(2, intentFilter.getValue().countActions());
+        assertEquals(UiModeManager.ACTION_ENTER_CAR_MODE, intentFilter.getValue().getAction(0));
+        assertEquals(UiModeManager.ACTION_EXIT_CAR_MODE, intentFilter.getValue().getAction(1));
+    }
+
+    public void testOnEnterExitCarMode() {
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        new SystemStateProvider(mContext).addListener(mSystemStateListener);
+
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+        when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE);
+        receiver.getValue().onReceive(mContext, mIntentEnter);
+        verify(mSystemStateListener).onCarModeChanged(true);
+
+        when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE);
+        receiver.getValue().onReceive(mContext, mIntentExit);
+        verify(mSystemStateListener).onCarModeChanged(false);
+
+        receiver.getValue().onReceive(mContext, new Intent("invalid action"));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index a6bbbe1..d3c3ff4 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
@@ -40,16 +39,11 @@
 import android.media.IAudioService;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.Handler;
 import android.os.Process;
 import android.os.UserHandle;
 import android.telecom.Call;
-import android.telecom.CallAudioState;
-import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
-import android.telecom.DisconnectCause;
-import android.telecom.InCallService;
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -57,11 +51,7 @@
 import android.telecom.VideoProfile;
 
 import com.android.internal.telecom.IInCallAdapter;
-import com.android.internal.telecom.IVideoProvider;
-import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.BluetoothPhoneServiceImpl;
-import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.CallIntentProcessor;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CallsManagerListenerBase;
@@ -70,13 +60,12 @@
 import com.android.server.telecom.HeadsetMediaButtonFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.InCallWakeLockControllerFactory;
-import com.android.server.telecom.Log;
 import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.components.PrimaryCallReceiver;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -85,16 +74,9 @@
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
-import java.io.StringWriter;
-import java.util.Map;
-import java.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
-
 /**
  * Implements mocks and functionality required to implement telecom system tests.
  */
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index d6585d9..bc7f9e1 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom.tests;
 
-import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.Log;
 
 import org.mockito.ArgumentCaptor;