Merge "Update Telecom test app to support bluetooth for self-managed calls."
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index dbe0137..d379444 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -1440,6 +1440,7 @@
             if ((mConnectionProperties & Connection.PROPERTY_IS_RTT) ==
                     Connection.PROPERTY_IS_RTT) {
                 createRttStreams();
+                mWasEverRtt = true;
                 if (isEmergencyCall()) {
                     mCallsManager.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
                     mCallsManager.mute(false);
@@ -2582,7 +2583,6 @@
         if (!areRttStreamsInitialized()) {
             Log.i(this, "Initializing RTT streams");
             try {
-                mWasEverRtt = true;
                 mInCallToConnectionServiceStreams = ParcelFileDescriptor.createReliablePipe();
                 mConnectionServiceToInCallStreams = ParcelFileDescriptor.createReliablePipe();
             } catch (IOException e) {
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index b5c7e7a..297414b 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -29,6 +29,12 @@
 import com.android.internal.util.StateMachine;
 
 public class CallAudioModeStateMachine extends StateMachine {
+    public static class Factory {
+        public CallAudioModeStateMachine create(AudioManager am) {
+            return new CallAudioModeStateMachine(am);
+        }
+    }
+
     public static class MessageArgs {
         public boolean hasActiveOrDialingCalls;
         public boolean hasRingingCalls;
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index fba17ae..a43f9aa 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -72,6 +72,24 @@
  */
 public class CallAudioRouteStateMachine extends StateMachine {
 
+    public static class Factory {
+        public CallAudioRouteStateMachine create(
+                Context context,
+                CallsManager callsManager,
+                BluetoothRouteManager bluetoothManager,
+                WiredHeadsetManager wiredHeadsetManager,
+                StatusBarNotifier statusBarNotifier,
+                CallAudioManager.AudioServiceFactory audioServiceFactory,
+                int earpieceControl) {
+            return new CallAudioRouteStateMachine(context,
+                    callsManager,
+                    bluetoothManager,
+                    wiredHeadsetManager,
+                    statusBarNotifier,
+                    audioServiceFactory,
+                    earpieceControl);
+        }
+    }
     /** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
     public static final int EARPIECE_FORCE_DISABLED = 0;
     public static final int EARPIECE_FORCE_ENABLED  = 1;
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index f67a7f7..2dde241 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -117,7 +117,7 @@
             }
         }
 
-        mHandler.post(new Runnable("CILH.sL", mLock) {
+        mHandler.post(new Runnable("CILH.sL", null) {
             @Override
             public void loggedRun() {
                 Session continuedSession = Log.createSubsession();
@@ -171,7 +171,7 @@
     }
 
     private void startPhotoLookup(final Uri handle, final Uri contactPhotoUri) {
-        mHandler.post(new Runnable("CILH.sPL", mLock) {
+        mHandler.post(new Runnable("CILH.sPL", null) {
             @Override
             public void loggedRun() {
                 Session continuedSession = Log.createSubsession();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 0f37fe4..9e7677b 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -17,12 +17,14 @@
 package com.android.server.telecom;
 
 import android.app.ActivityManager;
+import android.app.KeyguardManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.pm.UserInfo;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
+import android.media.AudioSystem;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -363,6 +365,8 @@
             InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
             ClockProxy clockProxy,
             BluetoothStateReceiver bluetoothStateReceiver,
+            CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
+            CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
             InCallControllerFactory inCallControllerFactory) {
         mContext = context;
         mLock = lock;
@@ -384,15 +388,16 @@
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
-        CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
-                context,
-                this,
-                bluetoothManager,
-                wiredHeadsetManager,
-                statusBarNotifier,
-                audioServiceFactory,
-                CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
-        );
+        CallAudioRouteStateMachine callAudioRouteStateMachine =
+                callAudioRouteStateMachineFactory.create(
+                        context,
+                        this,
+                        bluetoothManager,
+                        wiredHeadsetManager,
+                        statusBarNotifier,
+                        audioServiceFactory,
+                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+                );
         callAudioRouteStateMachine.initialize();
 
         CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
@@ -416,13 +421,12 @@
         mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext,
                 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE), mLock);
         mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
-                this,new CallAudioModeStateMachine((AudioManager)
+                this, callAudioModeStateMachineFactory.create((AudioManager)
                         mContext.getSystemService(Context.AUDIO_SERVICE)),
                 playerFactory, mRinger, new RingbackPlayer(playerFactory),
                 bluetoothStateReceiver, mDtmfLocalTonePlayer);
 
-        mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(
-                mRequester, Looper.getMainLooper());
+        mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(mRequester);
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
@@ -516,20 +520,6 @@
             return;
         }
 
-        // Check DISALLOW_OUTGOING_CALLS restriction.
-        // Only ecbm calls are allowed through when users with the DISALLOW_OUTGOING_CALLS
-        // restriction are the current user.
-        final UserManager userManager = (UserManager) mContext.getSystemService(
-                Context.USER_SERVICE);
-        if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                mCurrentUserHandle)) {
-            Log.w(this, "Rejecting non-ecbm phone call due to DISALLOW_INCOMING_CALLS "
-                    + "restriction");
-            incomingCall.reject(false, null);
-            mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE, false /* showNotification */);
-            return;
-        }
-
         List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
         filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
         filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter(),
@@ -1480,21 +1470,7 @@
             // Hold or disconnect the active call and request call focus for the incoming call.
             Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
             Log.d(this, "Incoming call = %s Ongoing call %s", call, activeCall);
-            if (activeCall != null && activeCall != call) {
-                // Hold the telephony call even if it doesn't have the hold capability.
-                if (canHold(activeCall)) {
-                    Log.d(this, "Answer %s, hold %s", call, activeCall);
-                    activeCall.hold();
-                } else {
-                    // 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 (activeCall.getConnectionService() != call.getConnectionService()) {
-                        activeCall.disconnect("Can't hold when answering " + call.getId());
-                    }
-                }
-            }
-
+            holdActiveCallForNewCall(call);
             mConnectionSvrFocusMgr.requestFocus(
                     call,
                     new RequestCallback(new ActionAnswerCall(call, videoState)));
@@ -1841,8 +1817,8 @@
     }
 
     private boolean isRttSettingOn() {
-        return Settings.System.getInt(mContext.getContentResolver(),
-                Settings.System.RTT_CALLING_MODE, 0) != 0;
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.RTT_CALLING_MODE, 0) != 0;
     }
 
     void phoneAccountSelected(Call call, PhoneAccountHandle account, boolean setDefault) {
@@ -1923,6 +1899,7 @@
         setCallState(call, CallState.DIALING, "dialing set explicitly");
         maybeMoveToSpeakerPhone(call);
         maybeTurnOffMute(call);
+        ensureCallAudible();
     }
 
     void markCallAsPulling(Call call) {
@@ -1930,11 +1907,43 @@
         maybeMoveToSpeakerPhone(call);
     }
 
-    void markCallAsActive(Call call) {
+    /**
+     * Returns true if the active call is held.
+     */
+    boolean holdActiveCallForNewCall(Call call) {
+        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+        if (activeCall != null && activeCall != call) {
+            if (canHold(activeCall)) {
+                activeCall.hold();
+                return true;
+            } else if (supportsHold(call)) {
+                Call heldCall = getHeldCallByConnectionService(call.getConnectionService());
+                if (heldCall != null) {
+                    heldCall.disconnect();
+                    Log.i(this, "Disconnecting held call %s before holding active call.", heldCall);
+                }
+                activeCall.hold();
+                return true;
+            } else {
+                // 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 (activeCall.getConnectionService() != call.getConnectionService()) {
+                    activeCall.disconnect();
+                }
+            }
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    public void markCallAsActive(Call call) {
         if (call.isSelfManaged()) {
             // backward compatibility, the self-managed connection service will set the call state
-            // to active directly. We should request the call focus for self-managed call before
-            // the state change
+            // to active directly. We should hold or disconnect the current active call based on the
+            // holdability, and request the call focus for the self-managed call before the state
+            // change.
+            holdActiveCallForNewCall(call);
             mConnectionSvrFocusMgr.requestFocus(
                     call,
                     new RequestCallback(new ActionSetCallState(
@@ -1944,6 +1953,7 @@
         } else {
             setCallState(call, CallState.ACTIVE, "active set explicitly");
             maybeMoveToSpeakerPhone(call);
+            ensureCallAudible();
         }
     }
 
@@ -2166,6 +2176,14 @@
         return getFirstCallWithState(CallState.ON_HOLD);
     }
 
+    public Call getHeldCallByConnectionService(ConnectionServiceWrapper connSvr) {
+        Optional<Call> heldCall = mCalls.stream()
+                .filter(call -> call.getConnectionService() == connSvr
+                        && call.getState() == CallState.ON_HOLD)
+                .findFirst();
+        return heldCall.isPresent() ? heldCall.get() : null;
+    }
+
     @VisibleForTesting
     public int getNumHeldCalls() {
         int count = 0;
@@ -2932,6 +2950,19 @@
         }
     }
 
+    private void ensureCallAudible() {
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+        if (am == null) {
+            Log.w(this, "ensureCallAudible: audio manager is null");
+            return;
+        }
+        if (am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) == 0) {
+            Log.i(this, "ensureCallAudible: voice call stream has volume 0. Adjusting to default.");
+            am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                    AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL), 0);
+        }
+    }
+
     /**
      * Creates a new call for an existing connection.
      *
@@ -3144,6 +3175,21 @@
         }
     }
 
+    public boolean isReplyWithSmsAllowed(int uid) {
+        UserHandle callingUser = UserHandle.of(UserHandle.getUserId(uid));
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
+
+        boolean isUserRestricted = userManager != null
+                && userManager.hasUserRestriction(UserManager.DISALLOW_SMS, callingUser);
+        boolean isLockscreenRestricted = keyguardManager != null
+                && keyguardManager.isDeviceLocked();
+        Log.d(this, "isReplyWithSmsAllowed: isUserRestricted: %s, isLockscreenRestricted: %s",
+                isUserRestricted, isLockscreenRestricted);
+
+        // TODO(hallliu): actually check the lockscreen once b/77731473 is fixed
+        return !isUserRestricted;
+    }
     /**
      * Blocks execution until all Telecom handlers have completed their current work.
      */
@@ -3749,7 +3795,7 @@
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
-    ConnectionServiceFocusManager getConnectionServiceFocusManager() {
+    public ConnectionServiceFocusManager getConnectionServiceFocusManager() {
         return mConnectionSvrFocusMgr;
     }
 
@@ -3757,6 +3803,10 @@
         return call.can(Connection.CAPABILITY_HOLD);
     }
 
+    private boolean supportsHold(Call call) {
+        return call.can(Connection.CAPABILITY_SUPPORT_HOLD);
+    }
+
     private final class ActionSetCallState implements PendingAction {
 
         private final Call mCall;
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 4bcc1ae..ede0ef6 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.telecom.Log;
@@ -30,14 +31,19 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 public class ConnectionServiceFocusManager {
     private static final String TAG = "ConnectionSvrFocusMgr";
+    private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
 
     /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
     public interface ConnectionServiceFocusManagerFactory {
-        ConnectionServiceFocusManager create(CallsManagerRequester requester, Looper looper);
+        ConnectionServiceFocusManager create(CallsManagerRequester requester);
     }
 
     /**
@@ -271,10 +277,12 @@
     private FocusManagerHandler mEventHandler;
 
     public ConnectionServiceFocusManager(
-            CallsManagerRequester callsManagerRequester, Looper looper) {
+            CallsManagerRequester callsManagerRequester) {
         mCallsManagerRequester = callsManagerRequester;
         mCallsManagerRequester.setCallsManagerListener(mCallsManagerListener);
-        mEventHandler = new FocusManagerHandler(looper);
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        mEventHandler = new FocusManagerHandler(handlerThread.getLooper());
         mCalls = new ArrayList<>();
     }
 
@@ -298,8 +306,32 @@
      * call is the current connection service focus. Also the state of the focus call must be one
      * of {@link #PRIORITY_FOCUS_CALL_STATE}.
      */
-    public CallFocus getCurrentFocusCall() {
-        return mCurrentFocusCall;
+    public @Nullable CallFocus getCurrentFocusCall() {
+        if (mEventHandler.getLooper().isCurrentThread()) {
+            // return synchronously if we're on the same thread.
+            return mCurrentFocusCall;
+        }
+        final BlockingQueue<Optional<CallFocus>> currentFocusedCallQueue =
+                new LinkedBlockingQueue<>(1);
+        mEventHandler.post(() -> {
+            currentFocusedCallQueue.offer(
+                    mCurrentFocusCall == null ? Optional.empty() : Optional.of(mCurrentFocusCall));
+        });
+        try {
+            Optional<CallFocus> syncCallFocus = currentFocusedCallQueue.poll(
+                    GET_CURRENT_FOCUS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            if (syncCallFocus != null) {
+                return syncCallFocus.orElse(null);
+            } else {
+                Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
+                        + " inaccurate result");
+                return mCurrentFocusCall;
+            }
+        } catch (InterruptedException e) {
+            Log.w(TAG, "Interrupted when waiting for synchronous current focus."
+                    + " Returning possibly inaccurate result.");
+            return mCurrentFocusCall;
+        }
     }
 
     /** Returns the current connection service focus. */
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 02692c5..f3afc9a 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -16,12 +16,9 @@
 
 package com.android.server.telecom;
 
-import android.content.Context;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 
@@ -35,16 +32,14 @@
  * binding to it. This adapter can receive commands and updates until the in-call app is unbound.
  */
 class InCallAdapter extends IInCallAdapter.Stub {
-    private final Context mContext;
     private final CallsManager mCallsManager;
     private final CallIdMapper mCallIdMapper;
     private final TelecomSystem.SyncRoot mLock;
     private final String mOwnerComponentName;
 
     /** Persists the specified parameters. */
-    public InCallAdapter(Context context, CallsManager callsManager, CallIdMapper callIdMapper,
+    public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper,
             TelecomSystem.SyncRoot lock, String ownerComponentName) {
-        mContext = context;
         mCallsManager = callsManager;
         mCallIdMapper = callIdMapper;
         mLock = lock;
@@ -101,17 +96,12 @@
     public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
         try {
             Log.startSession(LogUtils.Sessions.ICA_REJECT_CALL, mOwnerComponentName);
-            UserHandle callingUser = UserHandle.of(UserHandle.getUserId(Binder.getCallingUid()));
-            UserManager userManager = mContext.getSystemService(UserManager.class);
-
             // Check to make sure the in-call app's user isn't restricted from sending SMS. If so,
-            // silently drop the outgoing message.
-            if (rejectWithMessage && userManager.hasUserRestriction(
-                    UserManager.DISALLOW_SMS, callingUser)) {
+            // silently drop the outgoing message. Also drop message if the screen is locked.
+            if (!mCallsManager.isReplyWithSmsAllowed(Binder.getCallingUid())) {
                 rejectWithMessage = false;
                 textMessage = null;
             }
-
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 7aaa770..9d20d4a 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -1309,7 +1309,6 @@
         try {
             inCallService.setInCallAdapter(
                     new InCallAdapter(
-                            mContext,
                             mCallsManager,
                             mCallIdMapper,
                             mLock,
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index d84fca5..d9398e5 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -135,7 +135,7 @@
         CharSequence getAppLabel(String packageName);
     }
 
-    private static final String FILE_NAME = "phone-account-registrar-state.xml";
+    public static final String FILE_NAME = "phone-account-registrar-state.xml";
     @VisibleForTesting
     public static final int EXPECTED_STATE_VERSION = 9;
 
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 9ecdde2..bb7f3fb 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -195,6 +195,7 @@
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             IncomingCallNotifier incomingCallNotifier,
             InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
+            CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
             ClockProxy clockProxy) {
         mContext = context.getApplicationContext();
         LogUtils.initLogging(mContext);
@@ -229,7 +230,7 @@
                     }
                 });
         BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
-                new BluetoothAdapterProxy(), mLock);
+                new BluetoothAdapterProxy());
         BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
                 bluetoothDeviceManager, new Timeouts.Adapter());
         BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
@@ -279,6 +280,8 @@
                 toneGeneratorFactory,
                 clockProxy,
                 bluetoothStateReceiver,
+                callAudioRouteStateMachineFactory,
+                new CallAudioModeStateMachine.Factory(),
                 inCallControllerFactory);
 
         mIncomingCallNotifier = incomingCallNotifier;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index ff81b4d..2f42fe6 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -82,14 +82,14 @@
 
     private final LinkedHashMap<String, BluetoothDevice> mConnectedDevicesByAddress =
             new LinkedHashMap<>();
-    private final TelecomSystem.SyncRoot mLock;
+
+    // This lock only protects internal state -- it doesn't lock on anything going into Telecom.
+    private final Object mLock = new Object();
 
     private BluetoothRouteManager mBluetoothRouteManager;
     private BluetoothHeadsetProxy mBluetoothHeadsetService;
 
-    public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter,
-            TelecomSystem.SyncRoot lock) {
-        mLock = lock;
+    public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter) {
 
         if (bluetoothAdapter != null) {
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index a1c70fa..dbae50d 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -33,6 +33,7 @@
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.BluetoothAdapterProxy;
 import com.android.server.telecom.BluetoothPhoneServiceImpl;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
@@ -163,20 +164,13 @@
                                             phoneAccountRegistrar);
                                 }
                             },
-                            new ConnectionServiceFocusManager
-                                    .ConnectionServiceFocusManagerFactory() {
-                                @Override
-                                public ConnectionServiceFocusManager create(
-                                        ConnectionServiceFocusManager.CallsManagerRequester requester,
-                                        Looper looper) {
-                                    return new ConnectionServiceFocusManager(requester, looper);
-                                }
-                            },
+                            ConnectionServiceFocusManager::new,
                             new Timeouts.Adapter(),
                             new AsyncRingtonePlayer(),
                             new PhoneNumberUtilsAdapterImpl(),
                             new IncomingCallNotifier(context),
                             ToneGenerator::new,
+                            new CallAudioRouteStateMachine.Factory(),
                             new ClockProxy() {
                                 @Override
                                 public long currentTimeMillis() {
diff --git a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
index 9b2b3fb..4d548a1 100644
--- a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
@@ -49,7 +49,6 @@
 
 @RunWith(JUnit4.class)
 public class AsyncBlockCheckFilterTest extends TelecomTestCase {
-    @Mock private Context mContext;
     @Mock private BlockCheckerAdapter mBlockCheckerAdapter;
     @Mock private Call mCall;
     @Mock private CallFilterResultCallback mCallback;
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index e304d34..9240199 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -20,11 +20,13 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
@@ -548,8 +550,10 @@
         // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
         IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         verify(mConnectionServiceFixtureA.getTestDouble())
                 .hold(eq(outgoing.mConnectionId), any());
         mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
@@ -585,10 +589,15 @@
                 .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
 
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
+        waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
         verify(audioManager, timeout(TEST_TIMEOUT))
                 .setSpeakerphoneOn(true);
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
-        verify(audioManager, timeout(TEST_TIMEOUT))
+        waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+        // setSpeakerPhoneOn(false) gets called once during the call initiation phase
+        verify(audioManager, timeout(TEST_TIMEOUT).atLeast(2))
                 .setSpeakerphoneOn(false);
 
         mConnectionServiceFixtureA.
@@ -784,7 +793,7 @@
                 anyString(),
                 eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
                 eq(phoneNumber),
-                isNull(Bundle.class))).thenAnswer(answer);
+                nullable(Bundle.class))).thenAnswer(answer);
     }
 
     private void verifyNoBlockChecks() {
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 4a48f1b..157d11a 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -68,8 +68,7 @@
         device3 = makeBluetoothDevice("00:00:00:00:00:03");
 
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapterProxy,
-                new TelecomSystem.SyncRoot() { });
+        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapterProxy);
         mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
 
         ArgumentCaptor<BluetoothProfile.ServiceListener> serviceCaptor =
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 6bba7af..3fc3824 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -23,8 +23,6 @@
 import static org.mockito.ArgumentMatchers.anyChar;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -45,6 +43,8 @@
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
@@ -141,6 +141,10 @@
     @Mock private InCallControllerFactory mInCallControllerFactory;
     @Mock private InCallController mInCallController;
     @Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
+    @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    @Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
+    @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
+    @Mock private CallAudioModeStateMachine.Factory mCallAudioModeStateMachineFactory;
     @Mock private BluetoothStateReceiver mBluetoothStateReceiver;
 
     private CallsManager mCallsManager;
@@ -158,9 +162,13 @@
                 mProximitySensorManager);
         when(mInCallControllerFactory.create(any(), any(), any(), any(), any(), any(),
                 any())).thenReturn(mInCallController);
+        when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
+                anyInt())).thenReturn(mCallAudioRouteStateMachine);
+        when(mCallAudioModeStateMachineFactory.create(any()))
+                .thenReturn(mCallAudioModeStateMachine);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
         when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
-        when(mConnSvrFocusManagerFactory.create(any(), any())).thenReturn(mConnectionSvrFocusMgr);
+        when(mConnSvrFocusManagerFactory.create(any())).thenReturn(mConnectionSvrFocusMgr);
         mCallsManager = new CallsManager(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
                 mLock,
@@ -184,6 +192,8 @@
                 mToneGeneratorFactory,
                 mClockProxy,
                 mBluetoothStateReceiver,
+                mCallAudioRouteStateMachineFactory,
+                mCallAudioModeStateMachineFactory,
                 mInCallControllerFactory);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
@@ -421,6 +431,7 @@
         // GIVEN a CallsManager with ongoing call, and this call can be held
         Call ongoingCall = addSpyCall();
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call
@@ -430,11 +441,11 @@
         mCallsManager.unholdCall(heldCall);
 
         // THEN the ongoing call is held, and the focus request for incoming call is sent
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(any());
         verifyFocusRequestAndExecuteCallback(heldCall);
 
         // and held call is unhold now
-        verify(heldCall).unhold();
+        verify(heldCall).unhold(any());
     }
 
     @SmallTest
@@ -446,6 +457,7 @@
         // GIVEN a CallsManager with ongoing call, and this call can not be held
         Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has different ConnectionService
@@ -455,13 +467,14 @@
         mCallsManager.unholdCall(heldCall);
 
         // THEN the ongoing call is disconnected, and the focus request for incoming call is sent
-        verify(ongoingCall).disconnect();
+        verify(ongoingCall).disconnect(any());
         verifyFocusRequestAndExecuteCallback(heldCall);
 
         // and held call is unhold now
-        verify(heldCall).unhold();
+        verify(heldCall).unhold(any());
     }
 
+    @SmallTest
     @Test
     public void testUnholdCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
         ConnectionServiceWrapper connSvr = Mockito.mock(ConnectionServiceWrapper.class);
@@ -469,6 +482,7 @@
         // GIVEN a CallsManager with ongoing call, and this call can not be held
         Call ongoingCall = addSpyCallWithConnectionService(connSvr);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has different ConnectionService
@@ -478,11 +492,11 @@
         mCallsManager.unholdCall(heldCall);
 
         // THEN the ongoing call is held
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(any());
         verifyFocusRequestAndExecuteCallback(heldCall);
 
         // and held call is unhold now
-        verify(heldCall).unhold();
+        verify(heldCall).unhold(any());
     }
 
     @SmallTest
@@ -491,6 +505,7 @@
         // GIVEN a CallsManager with ongoing call, and this call can be held
         Call ongoingCall = addSpyCall();
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
@@ -528,13 +543,14 @@
 
     @SmallTest
     @Test
-    public void testANswerCallWhenOngoingHasDifferentConnectionService() {
+    public void testAnswerCallWhenOngoingHasDifferentConnectionService() {
         ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
         ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);
 
         // GIVEN a CallsManager with ongoing call, and this call can not be held
         Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
@@ -551,6 +567,48 @@
 
     @SmallTest
     @Test
+    public void testAnswerCallWhenMultipleHeldCallsExisted() {
+        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
+        ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);
+
+        // Given an ongoing call and held call with the ConnectionService connSvr1. The
+        // ConnectionService connSvr1 can handle one held call
+        Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        Call heldCall = addSpyCallWithConnectionService(connSvr1);
+        doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+        // and other held call has difference ConnectionService
+        Call heldCall2 = addSpyCallWithConnectionService(connSvr2);
+        doReturn(CallState.ON_HOLD).when(heldCall2).getState();
+
+        // WHEN answer an incoming call which ConnectionService is connSvr1
+        Call incomingCall = addSpyCallWithConnectionService(connSvr1);
+        doReturn(true).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the previous held call is disconnected
+        verify(heldCall).disconnect();
+
+        // and the ongoing call is held
+        verify(ongoingCall).hold();
+
+        // and the heldCall2 is not disconnected
+        verify(heldCall2, never()).disconnect();
+
+        // and the focus request is sent
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+
+        // and the incoming call is answered
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
     public void testAnswerCallWhenNoOngoingCallExisted() {
         // GIVEN a CallsManager with no ongoing call.
 
@@ -565,6 +623,83 @@
         verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
     }
 
+    @SmallTest
+    @Test
+    public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
+        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
+        ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);
+
+        // GIVEN a CallsManager with ongoing call, and this call can not be held
+        Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(ongoingCall).when(mConnectionSvrFocusMgr).getCurrentFocusCall();
+
+        // and a new self-managed call which has different ConnectionService
+        Call newCall = addSpyCallWithConnectionService(connSvr2);
+        doReturn(true).when(newCall).isSelfManaged();
+
+        // WHEN active the new call
+        mCallsManager.markCallAsActive(newCall);
+
+        // THEN the ongoing call is disconnected, and the focus request for the new call is sent
+        verify(ongoingCall).disconnect();
+        verifyFocusRequestAndExecuteCallback(newCall);
+
+        // and the new call is active
+        assertEquals(CallState.ACTIVE, newCall.getState());
+    }
+
+    @SmallTest
+    @Test
+    public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
+        ConnectionServiceWrapper connSvr = Mockito.mock(ConnectionServiceWrapper.class);
+
+        // GIVEN a CallsManager with ongoing call, and this call can not be held
+        Call ongoingCall = addSpyCallWithConnectionService(connSvr);
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a new self-managed call which has the same ConnectionService
+        Call newCall = addSpyCallWithConnectionService(connSvr);
+        doReturn(true).when(newCall).isSelfManaged();
+
+        // WHEN active the new call
+        mCallsManager.markCallAsActive(newCall);
+
+        // THEN the ongoing call isn't disconnected
+        verify(ongoingCall, never()).disconnect();
+        verifyFocusRequestAndExecuteCallback(newCall);
+
+        // and the new call is active
+        assertEquals(CallState.ACTIVE, newCall.getState());
+    }
+
+    @SmallTest
+    @Test
+    public void testSetActiveCallWhenOngoingCallCanBeHeld() {
+        // GIVEN a CallsManager with ongoing call, and this call can be held
+        Call ongoingCall = addSpyCall();
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(ongoingCall).when(mConnectionSvrFocusMgr).getCurrentFocusCall();
+
+        // and a new self-managed call
+        Call newCall = addSpyCall();
+        doReturn(true).when(newCall).isSelfManaged();
+
+        // WHEN active the new call
+        mCallsManager.markCallAsActive(newCall);
+
+        // THEN the ongoing call is held
+        verify(ongoingCall).hold();
+        verifyFocusRequestAndExecuteCallback(newCall);
+
+        // and the new call is active
+        assertEquals(CallState.ACTIVE, newCall.getState());
+    }
+
     private Call addSpyCallWithConnectionService(ConnectionServiceWrapper connSvr) {
         Call call = addSpyCall();
         doReturn(connSvr).when(call).getConnectionService();
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 9cc2b87..704bcd5 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -190,6 +190,8 @@
         public String getSystemServiceName(Class<?> svcClass) {
             if (svcClass == UserManager.class) {
                 return Context.USER_SERVICE;
+            } else if (svcClass == AudioManager.class) {
+                return Context.AUDIO_SERVICE;
             }
             throw new UnsupportedOperationException();
         }
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 3154b7d..6c4e2e0 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -286,7 +286,9 @@
 
         @Override
         public void rejectWithMessage(String callId, String message,
-                Session.Info info) throws RemoteException { }
+                Session.Info info) throws RemoteException {
+            rejectedCallIds.add(callId);
+        }
 
         @Override
         public void disconnect(String callId, Session.Info info) throws RemoteException { }
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
index 3c2cc61..77a9c0d 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom.tests;
 
-import android.os.Looper;
 import android.test.suitebuilder.annotation.SmallTest;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
@@ -63,8 +62,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mFocusManagerUT = new ConnectionServiceFocusManager(
-                mockCallsManagerRequester, Looper.getMainLooper());
+        mFocusManagerUT = new ConnectionServiceFocusManager(mockCallsManagerRequester);
         mNewCall = createFakeCall(mNewConnectionService, CallState.NEW);
         mActiveCall = createFakeCall(mActiveConnectionService, CallState.ACTIVE);
         ArgumentCaptor<CallsManager.CallsManagerListener> captor =
diff --git a/tests/src/com/android/server/telecom/tests/EventManagerTest.java b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
index 24394ec..8bf8d1d 100644
--- a/tests/src/com/android/server/telecom/tests/EventManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.telecom.tests;
 
+import android.net.Uri;
+import android.os.Build;
+import android.telecom.Log;
 import android.telecom.Logging.EventManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -217,4 +220,23 @@
         assertEquals(0, timings1.size());
         assertEquals(0, timings2.size());
     }
+
+    /**
+     * Ensure PII logging will log the last 2 digits of a phone number.
+     */
+    @SmallTest
+    @Test
+    public void testLogLast2DigitsPhone() {
+        if (Build.IS_USER) {
+            return;
+        }
+        assertEquals("tel:**********12",
+                Log.piiHandle(Uri.fromParts("tel", "+16505551212", null)));
+        assertEquals("tel:*****12",
+                Log.piiHandle(Uri.fromParts("tel", "5551212", null)));
+        assertEquals("tel:*11",
+                Log.piiHandle(Uri.fromParts("tel", "411", null)));
+        assertEquals("tel:1",
+                Log.piiHandle(Uri.fromParts("tel", "1", null)));
+    }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4cf7644..780b0b1 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -69,6 +69,7 @@
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.BluetoothPhoneServiceImpl;
 import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CallsManagerListenerBase;
@@ -85,8 +86,11 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.StatusBarNotifier;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -98,6 +102,7 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -402,6 +407,15 @@
     }
 
     private void setupTelecomSystem() throws Exception {
+        // Remove any cached PhoneAccount xml
+        File phoneAccountFile =
+                new File(mComponentContextFixture.getTestDouble()
+                        .getApplicationContext().getFilesDir(),
+                        PhoneAccountRegistrar.FILE_NAME);
+        if (phoneAccountFile.exists()) {
+            phoneAccountFile.delete();
+        }
+
         // Use actual implementations instead of mocking the interface out.
         HeadsetMediaButtonFactory headsetMediaButtonFactory =
                 spy(new HeadsetMediaButtonFactoryF());
@@ -422,45 +436,39 @@
         when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CREATE_ELAPSED_TIME);
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
-                new MissedCallNotifierImplFactory() {
-                    @Override
-                    public MissedCallNotifier makeMissedCallNotifierImpl(Context context,
-                            PhoneAccountRegistrar phoneAccountRegistrar,
-                            DefaultDialerCache defaultDialerCache) {
-                        return mMissedCallNotifier;
-                    }
-                },
+                (context, phoneAccountRegistrar, defaultDialerCache) -> mMissedCallNotifier,
                 mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
                 headsetMediaButtonFactory,
                 proximitySensorManagerFactory,
                 inCallWakeLockControllerFactory,
-                new CallAudioManager.AudioServiceFactory() {
-                    @Override
-                    public IAudioService getAudioService() {
-                        return mAudioService;
-                    }
-                },
-                new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
-                    @Override
-                    public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
-                            TelecomSystem.SyncRoot lock, CallsManager callsManager,
-                            PhoneAccountRegistrar phoneAccountRegistrar) {
-                        return mBluetoothPhoneServiceImpl;
-                    }
-                },
-                new ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory() {
-                    @Override
-                    public ConnectionServiceFocusManager create(
-                            ConnectionServiceFocusManager.CallsManagerRequester requester,
-                            Looper looper) {
-                        return new ConnectionServiceFocusManager(requester, looper);
-                    }
-                },
+                () -> mAudioService,
+                (context, lock, callsManager, phoneAccountRegistrar) -> mBluetoothPhoneServiceImpl,
+                ConnectionServiceFocusManager::new,
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
                 mPhoneNumberUtilsAdapter,
                 mIncomingCallNotifier,
                 (streamType, volume) -> mock(ToneGenerator.class),
+                new CallAudioRouteStateMachine.Factory() {
+                    @Override
+                    public CallAudioRouteStateMachine create(
+                            Context context,
+                            CallsManager callsManager,
+                            BluetoothRouteManager bluetoothManager,
+                            WiredHeadsetManager wiredHeadsetManager,
+                            StatusBarNotifier statusBarNotifier,
+                            CallAudioManager.AudioServiceFactory audioServiceFactory,
+                            int earpieceControl) {
+                        return new CallAudioRouteStateMachine(context,
+                                callsManager,
+                                bluetoothManager,
+                                wiredHeadsetManager,
+                                statusBarNotifier,
+                                audioServiceFactory,
+                                // Force enable an earpiece for the end-to-end tests
+                                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+                    }
+                },
                 mClockProxy);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
@@ -763,14 +771,16 @@
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
 
         // Wait for the focus tracker.
-        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        waitForHandlerAction(mTelecomSystem.getCallsManager()
+                .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
 
         verify(connectionServiceFixture.getTestDouble())
                 .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
                         eq(false)/*isIncoming*/, anyBoolean(), any());
         // Wait for handleCreateConnectionComplete
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
-        assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
+        assertEquals(startingNumConnections + 1,
+                connectionServiceFixture.mConnectionById.size());
 
         // Wait for the callback in ConnectionService#onAdapterAttached to execute.
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
@@ -972,6 +982,8 @@
 
             mInCallServiceFixtureX.mInCallAdapter
                     .answerCall(ids.mCallId, videoState);
+            // Wait on the main looper (due to the CS focus manager)
+            waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
             if (!VideoProfile.isVideo(videoState)) {
                 verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))