Merge "Fix call audio switch issue for BT LE to speaker and back to BT switch"
diff --git a/Android.bp b/Android.bp
index 88cffb8..1b422aa 100644
--- a/Android.bp
+++ b/Android.bp
@@ -54,7 +54,8 @@
         "androidx.legacy_legacy-support-core-utils",
         "androidx.core_core",
         "androidx.fragment_fragment",
-        "androidx.test.ext.junit"
+        "androidx.test.ext.junit",
+        "platform-compat-test-rules",
     ],
     srcs: [
         "tests/src/**/*.java",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index dbde12c..d376b6a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,6 +61,7 @@
     <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS"/>
     <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"/>
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
     <uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
 
     <permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
@@ -262,7 +263,7 @@
         <activity android:name=".RespondViaSmsSettings"
              android:label="@string/respond_via_sms_setting_title"
              android:configChanges="orientation|screenSize|keyboardHidden"
-             android:theme="@style/Theme.Telecom.DialerSettings"
+             android:theme="@style/CallSettingsWithoutDividerTheme"
              android:process=":ui"
              android:exported="true">
             <intent-filter>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 2e14650..acab8ef 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -15,6 +15,38 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsTelephony2TestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsTelephony3TestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsSimRestrictedApisTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    },
+    {
+      "name": "CtsTelephonyProviderTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ],
   "presubmit-large": [
diff --git a/res/values-night/styles.xml b/res/values-night/styles.xml
deleted file mode 100644
index da31304..0000000
--- a/res/values-night/styles.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 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.
--->
-
-<resources>
-
-    <style name="Theme.Telecom.DialerSettings" parent="@android:style/Theme.Material.Light">
-        <item name="android:forceDarkAllowed">true</item>
-        <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
-        <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
-        <item name="android:navigationBarColor">@color/background_color</item>
-        <item name="android:windowBackground">@color/background_color</item>
-        <item name="android:colorPrimaryDark">@color/background_color</item>
-        <item name="android:actionOverflowButtonStyle">@style/TelecomDialerSettingsActionOverflowButtonStyle</item>
-        <item name="android:windowContentOverlay">@null</item>
-    </style>
-
-    <style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.Material.Light">
-        <item name="android:forceDarkAllowed">true</item>
-        <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
-        <item name="android:navigationBarColor">@color/background_color</item>
-        <item name="android:windowBackground">@color/background_color</item>
-        <item name="android:colorPrimaryDark">@color/background_color</item>
-        <item name="android:windowContentOverlay">@null</item>
-        <item name="android:colorAccent">@color/theme_color</item>
-        <item name="android:listDivider">@null</item>
-    </style>
-
-</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 5ba0a3f..53e1bcb 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -28,33 +28,24 @@
 
    <style name="Theme.Telecom.DialerSettings" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:forceDarkAllowed">true</item>
-        <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
         <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
         <item name="android:actionOverflowButtonStyle">@style/TelecomDialerSettingsActionOverflowButtonStyle</item>
-        <item name="android:colorPrimaryDark">@color/background_color</item>
         <item name="android:windowLightStatusBar">true</item>
-        <item name="android:navigationBarColor">@color/background_color</item>
-        <item name="android:navigationBarDividerColor">@color/blocked_numbers_divider_color</item>
         <item name="android:windowLightNavigationBar">true</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
     <style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:forceDarkAllowed">true</item>
-        <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
         <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
-        <item name="android:colorPrimaryDark">@color/background_color</item>
         <item name="android:windowLightStatusBar">true</item>
-        <item name="android:navigationBarColor">@color/background_color</item>
-        <item name="android:navigationBarDividerColor">@color/blocked_numbers_divider_color</item>
         <item name="android:windowLightNavigationBar">true</item>
         <item name="android:windowContentOverlay">@null</item>
-        <item name="android:colorAccent">@color/theme_color</item>
         <item name="android:listDivider">@null</item>
     </style>
 
     <style name="TelecomDialerSettingsActionBarStyle" parent="android:Widget.DeviceDefault.ActionBar">
-        <item name="android:background">@color/background_color</item>
+        <item name="android:forceDarkAllowed">true</item>
         <item name="android:titleTextStyle">@style/BlockedNumbersTextHead1</item>
         <item name="android:elevation">@dimen/dialer_settings_actionbar_elevation</item>
         <!-- Empty icon -->
@@ -63,33 +54,36 @@
 
     <style name="TelecomDialerSettingsActionOverflowButtonStyle"
             parent="@android:style/Widget.DeviceDefault.Light.ActionButton.Overflow">
+        <item name="android:forceDarkAllowed">true</item>
         <item name="android:src">@drawable/ic_more_vert_24dp</item>
     </style>
 
     <style name="BlockedNumbersButton" parent="BlockedNumbersTextPrimary2">
-        <item name="android:textColor">@color/theme_color</item>
     </style>
 
     <style name="BlockedNumbersTextHead1"
            parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
-        <item name="android:textColor">@color/blocked_numbers_primary_text_color</item>
+        <item name="android:forceDarkAllowed">true</item>
         <item name="android:textSize">@dimen/blocked_numbers_head1_font_size</item>
         <item name="android:fontFamily">sans-serif-regular</item>
     </style>
 
-    <style name="BlockedNumbersTextPrimary2">
-        <item name="android:textColor">@color/blocked_numbers_primary_text_color</item>
+    <style name="BlockedNumbersTextPrimary2" parent="Theme.Telecom.BlockedNumbers">
         <item name="android:textSize">@dimen/blocked_numbers_primary2_font_size</item>
         <item name="android:fontFamily">sans-serif-regular</item>
         <item name="android:lineSpacingExtra">@dimen/blocked_numbers_line_spacing</item>
         <item name="android:capitalize">sentences</item>
     </style>
 
-    <style name="BlockedNumbersTextSecondary">
-        <item name="android:textColor">@color/blocked_numbers_secondary_text_color</item>
+    <style name="BlockedNumbersTextSecondary" parent="Theme.Telecom.BlockedNumbers">
         <item name="android:textSize">@dimen/blocked_numbers_secondary_font_size</item>
         <item name="android:fontFamily">sans-serif-regular</item>
         <item name="android:lineSpacingExtra">@dimen/blocked_numbers_secondary_line_spacing</item>
         <item name="android:capitalize">sentences</item>
     </style>
+
+    <style name="CallSettingsWithoutDividerTheme" parent="Theme.Telecom.DialerSettings">
+        <item name="android:listDivider">@null</item>
+    </style>
+
 </resources>
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index d7786e1..7f51f1b 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -172,13 +172,13 @@
             // Use haptic-only ringtone or do not play anything.
             if (!isRingerAudible || Uri.EMPTY.equals(incomingCall.getRingtone())) {
                 if (isVibrationEnabled) {
-                    mRingtone = factory.getHapticOnlyRingtone();
+                    setRingtone(factory.getHapticOnlyRingtone());
                     if (mRingtone == null) {
                         completeHapticFuture(false /* ringtoneHasHaptics */);
                         return;
                     }
                 } else {
-                    mRingtone = null;
+                    setRingtone(null);
                     completeHapticFuture(false /* ringtoneHasHaptics */);
                     return;
                 }
@@ -188,7 +188,7 @@
             Log.i(this, "handlePlay: Play ringtone.");
 
             if (mRingtone == null) {
-                mRingtone = factory.getRingtone(incomingCall, volumeShaperConfig);
+                setRingtone(factory.getRingtone(incomingCall, volumeShaperConfig));
                 if (mRingtone == null) {
                     Uri ringtoneUri = incomingCall.getRingtone();
                     String ringtoneUriString = (ringtoneUri == null) ? "null" :
@@ -209,7 +209,7 @@
                         isVibrationEnabled);
                 SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
                 if (hasHaptics && (volumeShaperConfig == null
-                        || systemSettingsUtil.enableAudioCoupledVibrationForRampingRinger())) {
+                        || systemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())) {
                     AudioAttributes attributes = mRingtone.getAudioAttributes();
                     Log.d(this, "handlePlay: %s haptic channel",
                             (isVibrationEnabled ? "unmuting" : "muting"));
@@ -240,11 +240,7 @@
         ThreadUtil.checkNotOnMainThread();
         Log.i(this, "Stop ringtone.");
 
-        if (mRingtone != null) {
-            Log.d(this, "Ringtone.stop() invoked.");
-            mRingtone.stop();
-            mRingtone = null;
-        }
+        setRingtone(null);
 
         synchronized(this) {
             if (mHandler.hasMessages(EVENT_PLAY)) {
@@ -262,6 +258,17 @@
         return mRingtone != null;
     }
 
+    private void setRingtone(@Nullable Ringtone ringtone) {
+        // Make sure that any previously created instance of Ringtone is stopped so the MediaPlayer
+        // can be released, before replacing mRingtone with a new instance. This is always created
+        // as a looping Ringtone, so if not stopped it will keep playing on the background.
+        if (mRingtone != null) {
+            Log.d(this, "Ringtone.stop() invoked.");
+            mRingtone.stop();
+        }
+        mRingtone = ringtone;
+    }
+
     private void completeHapticFuture(boolean ringtoneHasHaptics) {
         if (mHapticsFuture != null) {
             mHapticsFuture.complete(ringtoneHasHaptics);
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 08dfb1e..60016fd 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -898,7 +898,7 @@
     @Override
     public String toString() {
         return String.format(Locale.US, "[Call id=%s, state=%s, tpac=%s, cmgr=%s, handle=%s, "
-                        + "vidst=%s, childs(%d), has_parent(%b), cap=%s, prop=%s]",
+                        + "vidst=%s, childs(%d), has_parent(%b), cap=%s, prop=%s], voip=%b",
                 mId,
                 CallState.toString(getParcelableCallState()),
                 getTargetPhoneAccount(),
@@ -908,7 +908,8 @@
                 getChildCalls().size(),
                 getParentCall() != null,
                 Connection.capabilitiesToStringShort(getConnectionCapabilities()),
-                Connection.propertiesToStringShort(getConnectionProperties()));
+                Connection.propertiesToStringShort(getConnectionProperties()),
+                mIsVoipAudioMode);
     }
 
     @Override
@@ -1262,7 +1263,7 @@
         }
     }
 
-    boolean isRingbackRequested() {
+    public boolean isRingbackRequested() {
         return mRingbackRequested;
     }
 
@@ -3653,6 +3654,9 @@
     }
 
     public void setIsVoipAudioMode(boolean audioModeIsVoip) {
+        if (mIsVoipAudioMode != audioModeIsVoip) {
+            Log.addEvent(this, LogUtils.Events.SET_VOIP_MODE, audioModeIsVoip ? "Y" : "N");
+        }
         mIsVoipAudioMode = audioModeIsVoip;
         for (Listener l : mListeners) {
             l.onIsVoipAudioModeChanged(this);
@@ -4138,8 +4142,8 @@
         return mStartRingTime;
     }
 
-    public void setStartRingTime(long startRingTime) {
-        mStartRingTime = startRingTime;
+    public void setStartRingTime() {
+        mStartRingTime = mClockProxy.elapsedRealtime();
     }
 
     public CharSequence getCallScreeningAppName() {
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 6a7261e..1863cde 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -107,6 +107,10 @@
             // No audio management for calls in a conference, or external calls.
             return;
         }
+        if (oldState == newState) {
+            // State did not change, so no need to do anything.
+            return;
+        }
         Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
                 CallState.toString(oldState), CallState.toString(newState));
 
@@ -464,8 +468,13 @@
     @VisibleForTesting
     public boolean startRinging() {
         synchronized (mCallsManager.getLock()) {
-            return mRinger.startRinging(mForegroundCall,
+            Call localForegroundCall = mForegroundCall;
+            boolean result = mRinger.startRinging(localForegroundCall,
                     mCallAudioRouteStateMachine.isHfpDeviceAvailable());
+            if (result) {
+                localForegroundCall.setStartRingTime();
+            }
+            return result;
         }
     }
 
@@ -537,14 +546,14 @@
         pw.println("Foreground call:");
         pw.println(mForegroundCall);
 
-        pw.println("CallAudioModeStateMachine pending messages:");
+        pw.println("CallAudioModeStateMachine:");
         pw.increaseIndent();
-        mCallAudioModeStateMachine.dumpPendingMessages(pw);
+        mCallAudioModeStateMachine.dump(pw);
         pw.decreaseIndent();
 
-        pw.println("CallAudioRouteStateMachine pending messages:");
+        pw.println("CallAudioRouteStateMachine:");
         pw.increaseIndent();
-        mCallAudioRouteStateMachine.dumpPendingMessages(pw);
+        mCallAudioRouteStateMachine.dump(pw);
         pw.decreaseIndent();
 
         pw.println("BluetoothDeviceManager:");
@@ -557,6 +566,7 @@
 
     @VisibleForTesting
     public void setIsTonePlaying(boolean isTonePlaying) {
+        Log.i(this, "setIsTonePlaying; isTonePlaying=%b", isTonePlaying);
         mIsTonePlaying = isTonePlaying;
         mCallAudioModeStateMachine.sendMessageWithArgs(
                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
@@ -728,10 +738,31 @@
                 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
                 .setIsTonePlaying(mIsTonePlaying)
                 .setForegroundCallIsVoip(
-                        mForegroundCall != null && mForegroundCall.getIsVoipAudioMode())
+                        mForegroundCall != null && isCallVoip(mForegroundCall))
                 .setSession(Log.createSubsession()).build();
     }
 
+    /**
+     * Determines if a {@link Call} is a VOIP call for audio purposes.
+     * For top level calls, we get this from {@link Call#getIsVoipAudioMode()}.  A {@link Call}
+     * representing a {@link android.telecom.Conference}, however, has no means of specifying that
+     * it is a VOIP conference, so we will get that attribute from one of the children.
+     * @param call The call.
+     * @return {@code true} if the call is a VOIP call, {@code false} if is a SIM call.
+     */
+    @VisibleForTesting
+    public boolean isCallVoip(Call call) {
+        if (call.isConference() && call.getChildCalls() != null
+                && call.getChildCalls().size() > 0 ) {
+            // If this is a conference with children, we can get the VOIP audio mode attribute from
+            // one of the children.  The Conference doesn't have a VOIP audio mode property, so we
+            // need to infer from the first child.
+            Call firstChild = call.getChildCalls().get(0);
+            return firstChild.getIsVoipAudioMode();
+        }
+        return call.getIsVoipAudioMode();
+    }
+
     private HashSet<Call> getBinForCall(Call call) {
         if (call.getState() == CallState.ANSWERED) {
             // If the call has the speed-up-mt-audio capability, treat answered state as active
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 2aa9d5d..a1c5f4b 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -22,6 +22,7 @@
 import android.telecom.Log;
 import android.telecom.Logging.Runnable;
 import android.telecom.Logging.Session;
+import android.util.LocalLog;
 import android.util.SparseArray;
 
 import com.android.internal.util.IState;
@@ -30,6 +31,11 @@
 import com.android.internal.util.StateMachine;
 
 public class CallAudioModeStateMachine extends StateMachine {
+    /**
+     * Captures the most recent CallAudioModeStateMachine state transitions and the corresponding
+     * changes to the {@link AudioManager#setMode}.
+     */
+    private LocalLog mLocalLog = new LocalLog(20);
     public static class Factory {
         public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
                 AudioManager am) {
@@ -227,9 +233,12 @@
     private class UnfocusedState extends BaseState {
         @Override
         public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering UNFOCUSED state");
+            mLocalLog.log("Enter UNFOCUSED");
             if (mIsInitialized) {
                 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
                 mAudioManager.setMode(AudioManager.MODE_NORMAL);
+                mLocalLog.log("Mode MODE_NORMAL");
                 mMostRecentMode = AudioManager.MODE_NORMAL;
                 // Don't release focus here -- wait until we get a signal that any other audio
                 // operations triggered by this are done before releasing focus.
@@ -290,9 +299,12 @@
     private class AudioProcessingFocusState extends BaseState {
         @Override
         public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering AUDIO_PROCESSING state");
+            mLocalLog.log("Enter AUDIO_PROCESSING");
             if (mIsInitialized) {
                 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
                 mAudioManager.setMode(NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING);
+                mLocalLog.log("Mode MODE_CALL_SCREENING");
                 mMostRecentMode = NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING;
             }
         }
@@ -371,6 +383,7 @@
                 // trips up the audio system.
                 if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
                     mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+                    mLocalLog.log("Mode MODE_RINGTONE");
                 }
                 mCallAudioManager.setCallAudioRouteFocusState(
                         CallAudioRouteStateMachine.RINGING_FOCUS);
@@ -383,6 +396,7 @@
         @Override
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering RINGING state");
+            mLocalLog.log("Enter RINGING");
             tryStartRinging();
             mCallAudioManager.stopCallWaiting();
         }
@@ -456,9 +470,11 @@
         @Override
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
+            mLocalLog.log("Enter SIM_CALL");
             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+            mLocalLog.log("Mode MODE_IN_CALL");
             mMostRecentMode = AudioManager.MODE_IN_CALL;
             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
@@ -537,9 +553,11 @@
         @Override
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
+            mLocalLog.log("Enter VOIP_CALL");
             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+            mLocalLog.log("Mode MODE_IN_COMMUNICATION");
             mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
@@ -614,9 +632,11 @@
         @Override
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
+            mLocalLog.log("Enter TONE/HOLDING");
             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
             mAudioManager.setMode(mMostRecentMode);
+            mLocalLog.log("Mode " + mMostRecentMode);
             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
         }
 
@@ -763,6 +783,13 @@
         getHandler().getLooper().dump(pw::println, "");
     }
 
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("History:");
+        mLocalLog.dump(pw);
+        pw.println("Pending Msg:");
+        dumpPendingMessages(pw);
+    }
+
     @Override
     protected void onPostHandleMessage(Message msg) {
         Log.endSession();
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
index 2b6ba64..d96f953 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -45,6 +45,16 @@
         return mBluetoothRouteManager.isBluetoothAudioConnectedOrPending();
     }
 
+    public boolean isHearingAidDeviceOn() {
+        return mBluetoothRouteManager.isCachedHearingAidDevice(
+                mBluetoothRouteManager.getBluetoothAudioConnectedDevice());
+    }
+
+    public boolean isLeAudioDeviceOn() {
+        return mBluetoothRouteManager.isCachedLeAudioDevice(
+                mBluetoothRouteManager.getBluetoothAudioConnectedDevice());
+    }
+
     @Override
     public void onBluetoothDeviceListChanged() {
         mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f6f710e..2ed7d95 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -411,6 +411,8 @@
                         Log.w(this, "Ignoring switch to headset command. Not available.");
                     }
                     return HANDLED;
+                case CONNECT_DOCK:
+                    // fall through; we want to switch to speaker mode when docked and in a call.
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
                     setSpeakerphoneOn(true);
@@ -486,6 +488,8 @@
                         Log.w(this, "Ignoring switch to headset command. Not available.");
                     }
                     return HANDLED;
+                case CONNECT_DOCK:
+                    // fall through; we want to go to the quiescent speaker route when out of a call
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
                     transitionTo(mQuiescentSpeakerRoute);
@@ -537,10 +541,6 @@
                 case BT_AUDIO_DISCONNECTED:
                     // This may be sent as a confirmation by the BT stack after switch off BT.
                     return HANDLED;
-                case CONNECT_DOCK:
-                    setSpeakerphoneOn(true);
-                    sendInternalMessage(SWITCH_SPEAKER);
-                    return HANDLED;
                 case DISCONNECT_DOCK:
                     // Nothing to do here
                     return HANDLED;
@@ -1273,6 +1273,8 @@
                 case SPEAKER_ON:
                     // Nothing to do
                     return HANDLED;
+                case DISCONNECT_DOCK:
+                    // Fall-through; same as if speaker goes off, we want to switch baseline.
                 case SPEAKER_OFF:
                     sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
                     return HANDLED;
@@ -1619,6 +1621,15 @@
         quitNow();
     }
 
+    public void dump(IndentingPrintWriter pw) {
+        pw.print("Current state: ");
+        pw.println(getCurrentState().getName());
+        pw.println("Pending messages:");
+        pw.increaseIndent();
+        dumpPendingMessages(pw);
+        pw.decreaseIndent();
+    }
+
     public void dumpPendingMessages(IndentingPrintWriter pw) {
         getHandler().getLooper().dump(pw::println, "");
     }
@@ -1629,8 +1640,26 @@
 
     private void setSpeakerphoneOn(boolean on) {
         Log.i(this, "turning speaker phone %s", on);
-        mAudioManager.setSpeakerphoneOn(on);
-        mStatusBarNotifier.notifySpeakerphone(on);
+        AudioDeviceInfo speakerDevice = null;
+        for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+            if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                speakerDevice = info;
+                break;
+            }
+        }
+        boolean speakerOn = false;
+        if (speakerDevice != null && on) {
+            boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+            if (result) {
+                speakerOn = true;
+            }
+        } else {
+            AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+            if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                mAudioManager.clearCommunicationDevice();
+            }
+        }
+        mStatusBarNotifier.notifySpeakerphone(speakerOn);
     }
 
     private void setBluetoothOn(String address) {
@@ -1678,8 +1707,8 @@
                     // may run as a separate user from the foreground user. If we
                     // used AudioManager directly, we would change mute for the system's
                     // user and not the current foreground, which we want to avoid.
-                    audio.setMicrophoneMute(
-                            mute, mContext.getOpPackageName(), getCurrentUserId());
+                    audio.setMicrophoneMute(mute, mContext.getOpPackageName(),
+                            getCurrentUserId(), mContext.getAttributionTag());
                 } catch (RemoteException e) {
                     Log.e(this, e, "Remote exception while toggling mute.");
                 }
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
index 9435250..167bd1b 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -145,9 +145,9 @@
 
         if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) {
             Log.i(this, "bindAndGetCallIdentification - bind failed");
-            Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName);
             mFuture.complete(null);
         }
+        Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName);
 
         // Set up a timeout so that we're not waiting forever for the caller ID information.
         Handler handler = new Handler();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 6757ead..4d829bb 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -17,6 +17,10 @@
 package com.android.server.telecom;
 
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.provider.CallLog.Calls.SHORT_RING_THRESHOLD;
+import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
+import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
 import static android.telecom.TelecomManager.ACTION_POST_CALL;
 import static android.telecom.TelecomManager.DURATION_LONG;
 import static android.telecom.TelecomManager.DURATION_MEDIUM;
@@ -136,6 +140,7 @@
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -339,10 +344,8 @@
     private RespondViaSmsManager mRespondViaSmsManager;
     private final Ringer mRinger;
     private final InCallWakeLockController mInCallWakeLockController;
-    // For this set initial table size to 16 because we add 13 listeners in
-    // the CallsManager constructor.
-    private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap(
-            new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
+    private final CopyOnWriteArrayList<CallsManagerListener> mListeners =
+            new CopyOnWriteArrayList<>();
     private final HeadsetMediaButton mHeadsetMediaButton;
     private final WiredHeadsetManager mWiredHeadsetManager;
     private final SystemStateHelper mSystemStateHelper;
@@ -575,7 +578,6 @@
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
         mListeners.add(mCallLogManager);
-        mListeners.add(mPhoneStateBroadcaster);
         mListeners.add(mInCallController);
         mListeners.add(mCallDiagnosticServiceController);
         mListeners.add(mCallAudioManager);
@@ -586,6 +588,9 @@
         mListeners.add(mProximitySensorManager);
         mListeners.add(audioProcessingNotification);
 
+        // this needs to be after the mCallAudioManager
+        mListeners.add(mPhoneStateBroadcaster);
+
         // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
         final UserManager userManager = UserManager.get(mContext);
         // Don't load missed call if it is run in split user model.
@@ -2081,7 +2086,7 @@
                                           int videoState, boolean shouldCancelCall,
                                           String uiAction) {
         Log.i(this, "onCallRedirectionComplete for Call %s with handle %s" +
-                " and phoneAccountHandle %s", call, handle, phoneAccountHandle);
+                " and phoneAccountHandle %s", call, Log.pii(handle), phoneAccountHandle);
 
         boolean endEarly = false;
         String disconnectReason = "";
@@ -2193,7 +2198,7 @@
      * @param callId The ID of the call to show the redirection dialog for.
      */
     private void showRedirectionDialog(@NonNull String callId, @NonNull CharSequence appName) {
-        AlertDialog confirmDialog = FrameworksUtils.makeAlertDialogBuilder(mContext).create();
+        AlertDialog confirmDialog = (new AlertDialog.Builder(mContext)).create();
         LayoutInflater layoutInflater = LayoutInflater.from(mContext);
         View dialogView = layoutInflater.inflate(R.layout.call_redirection_confirm_dialog, null);
 
@@ -3142,6 +3147,22 @@
             // be marked as missed.
             call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
         }
+        if (call.getState() == CallState.NEW
+                && disconnectCause.getCode() == DisconnectCause.MISSED) {
+            Log.i(this, "markCallAsDisconnected: missed call never rang ", call.getId());
+            call.setMissedReason(USER_MISSED_NEVER_RANG);
+        }
+        if (call.getState() == CallState.RINGING
+                || call.getState() == CallState.SIMULATED_RINGING) {
+            if (call.getStartRingTime() > 0
+                    && (mClockProxy.elapsedRealtime() - call.getStartRingTime())
+                    < SHORT_RING_THRESHOLD) {
+                Log.i(this, "markCallAsDisconnected; callid=%s, short ring.", call.getId());
+                call.setUserMissed(USER_MISSED_SHORT_RING);
+            } else if (call.getStartRingTime() > 0) {
+                call.setUserMissed(USER_MISSED_NO_ANSWER);
+            }
+        }
 
         // If a call diagnostic service is in use, we will log the original telephony-provided
         // disconnect cause, inform the CDS of the disconnection, and then chain the update of the
@@ -3300,7 +3321,7 @@
      *
      * @return {@code True} if there are any non-external calls, {@code false} otherwise.
      */
-    boolean hasAnyCalls() {
+    public boolean hasAnyCalls() {
         if (mCalls.isEmpty()) {
             return false;
         }
@@ -3987,6 +4008,19 @@
         return false;
     }
 
+    /**
+     * Determines if there are any ongoing self managed calls for the given package/user.
+     * @param packageName The package name to check.
+     * @param userHandle The userhandle to check.
+     * @return {@code true} if the app has ongoing calls, or {@code false} otherwise.
+     */
+    public boolean isInSelfManagedCall(String packageName, UserHandle userHandle) {
+        return mCalls.stream().anyMatch(
+                c -> c.isSelfManaged()
+                && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
+                && c.getTargetPhoneAccount().getUserHandle().equals(userHandle));
+    }
+
     @VisibleForTesting
     public int getNumCallsWithState(final boolean isSelfManaged, Call excludeCall,
                                     PhoneAccountHandle phoneAccountHandle, int... states) {
@@ -5595,4 +5629,9 @@
         mCalls.forEach(c -> Log.addEvent(c, LogUtils.Events.USER_LOG_MARK, message));
         Log.addEvent(null /* global */, LogUtils.Events.USER_LOG_MARK, message);
     }
+
+    @VisibleForTesting
+    public Ringer getRinger() {
+        return mRinger;
+    }
 }
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 3a4d143..5bb1dbe 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -62,6 +62,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.Objects;
 
 /**
  * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
@@ -967,6 +968,31 @@
 
                         if (alreadyAddedConnection != null
                                 && mCallIdMapper.getCall(callId) == null) {
+                            if (!Objects.equals(connection.getHandle(),
+                                    alreadyAddedConnection.getHandle())) {
+                                alreadyAddedConnection.setHandle(connection.getHandle());
+                            }
+                            if (connection.getHandlePresentation() !=
+                                    alreadyAddedConnection.getHandlePresentation()) {
+                                alreadyAddedConnection.setHandle(connection.getHandle(),
+                                        connection.getHandlePresentation());
+                            }
+                            if (!Objects.equals(connection.getCallerDisplayName(),
+                                    alreadyAddedConnection.getCallerDisplayName())) {
+                                alreadyAddedConnection.setCallerDisplayName(connection
+                                                .getCallerDisplayName(),
+                                        connection.getCallerDisplayNamePresentation());
+                            }
+                            if (connection.getConnectionCapabilities() !=
+                                    alreadyAddedConnection.getConnectionCapabilities()) {
+                                alreadyAddedConnection.setConnectionCapabilities(connection
+                                        .getConnectionCapabilities());
+                            }
+                            if (connection.getConnectionProperties() !=
+                                    alreadyAddedConnection.getConnectionProperties()) {
+                                alreadyAddedConnection.setConnectionCapabilities(connection
+                                        .getConnectionProperties());
+                            }
                             mCallIdMapper.addCall(alreadyAddedConnection, callId);
                             alreadyAddedConnection
                                     .replaceConnectionService(ConnectionServiceWrapper.this);
diff --git a/src/com/android/server/telecom/DockManager.java b/src/com/android/server/telecom/DockManager.java
index 46b2efc..dda5711 100644
--- a/src/com/android/server/telecom/DockManager.java
+++ b/src/com/android/server/telecom/DockManager.java
@@ -29,7 +29,12 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-/** Listens for and caches car dock state. */
+/**
+ * Listens for and caches car dock state.
+ * Testing; you can enable/disable dock with adb commands:
+ *   adb shell dumpsys DockObserver set state 3
+ *   adb shell dumpsys DockObserver set state 0
+ */
 @VisibleForTesting
 public class DockManager {
     @VisibleForTesting
diff --git a/src/com/android/server/telecom/FrameworksUtils.java b/src/com/android/server/telecom/FrameworksUtils.java
deleted file mode 100644
index 08a19f5..0000000
--- a/src/com/android/server/telecom/FrameworksUtils.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 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.AlertDialog;
-import android.content.Context;
-import android.content.res.Configuration;
-
-/**
- * This class provides utility functions over framework APIs
- */
-public class FrameworksUtils {
-    /**
-     * Create a new instance of {@link AlertDialog.Builder}.
-     * @param context reference to a Context
-     * @return an instance of AlertDialog.Builder
-     */
-    public static AlertDialog.Builder makeAlertDialogBuilder(Context context) {
-        boolean isDarkTheme = (context.getResources().getConfiguration().uiMode
-                & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
-        return new AlertDialog.Builder(context, isDarkTheme
-                ? android.R.style.Theme_DeviceDefault_Dialog_Alert : 0);
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index ad95c34..b1471c2 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -46,6 +46,47 @@
     private static final int MSG_MEDIA_SESSION_INITIALIZE = 0;
     private static final int MSG_MEDIA_SESSION_SET_ACTIVE = 1;
 
+    /**
+     * Wrapper class that abstracts an instance of {@link MediaSession} to the
+     * {@link MediaSessionAdapter} interface this class uses.  This is done because
+     * {@link MediaSession} is a final class and cannot be mocked for testing purposes.
+     */
+    public class MediaSessionWrapper implements MediaSessionAdapter {
+        private final MediaSession mMediaSession;
+
+        public MediaSessionWrapper(MediaSession mediaSession) {
+            mMediaSession = mediaSession;
+        }
+
+        /**
+         * Sets the underlying {@link MediaSession} active status.
+         * @param active
+         */
+        @Override
+        public void setActive(boolean active) {
+            mMediaSession.setActive(active);
+        }
+
+        /**
+         * Gets the underlying {@link MediaSession} active status.
+         * @return {@code true} if active, {@code false} otherwise.
+         */
+        @Override
+        public boolean isActive() {
+            return mMediaSession.isActive();
+        }
+    }
+
+    /**
+     * Interface which defines the basic functionality of a {@link MediaSession} which is important
+     * for the {@link HeadsetMediaButton} to operator; this is for testing purposes so we can mock
+     * out that functionality.
+     */
+    public interface MediaSessionAdapter {
+        void setActive(boolean active);
+        boolean isActive();
+    }
+
     private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
         @Override
         public boolean onMediaButtonEvent(Intent intent) {
@@ -81,7 +122,7 @@
                     session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
                             | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
                     session.setPlaybackToLocal(AUDIO_ATTRIBUTES);
-                    mSession = session;
+                    mSession = new MediaSessionWrapper(session);
                     break;
                 }
                 case MSG_MEDIA_SESSION_SET_ACTIVE: {
@@ -102,9 +143,37 @@
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
-    private MediaSession mSession;
+    private MediaSessionAdapter mSession;
     private KeyEvent mLastHookEvent;
 
+    /**
+     * Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
+     * specified {@link MediaSessionAdapter}.  Will not trigger MSG_MEDIA_SESSION_INITIALIZE and
+     * cause an actual {@link MediaSession} instance to be created.
+     * @param context the context
+     * @param callsManager the mock calls manager
+     * @param lock the lock
+     * @param adapter the adapter
+     */
+    @VisibleForTesting
+    public HeadsetMediaButton(
+            Context context,
+            CallsManager callsManager,
+            TelecomSystem.SyncRoot lock,
+            MediaSessionAdapter adapter) {
+        mContext = context;
+        mCallsManager = callsManager;
+        mLock = lock;
+        mSession = adapter;
+    }
+
+    /**
+     * Production code constructor; this version triggers MSG_MEDIA_SESSION_INITIALIZE which will
+     * create an actual instance of {@link MediaSession}.
+     * @param context the context
+     * @param callsManager the calls manager
+     * @param lock the telecom lock
+     */
     public HeadsetMediaButton(
             Context context,
             CallsManager callsManager,
@@ -155,6 +224,13 @@
         if (call.isExternalCall()) {
             return;
         }
+        handleCallAddition();
+    }
+
+    /**
+     * Triggers session activation due to call addition.
+     */
+    private void handleCallAddition() {
         mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
     }
 
@@ -164,6 +240,13 @@
         if (call.isExternalCall()) {
             return;
         }
+        handleCallRemoval();
+    }
+
+    /**
+     * Triggers session deactivation due to call removal.
+     */
+    private void handleCallRemoval() {
         if (!mCallsManager.hasAnyCalls()) {
             mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
         }
@@ -172,10 +255,20 @@
     /** ${inheritDoc} */
     @Override
     public void onExternalCallChanged(Call call, boolean isExternalCall) {
+        // Note: We don't use the onCallAdded/onCallRemoved methods here since they do checks to see
+        // if the call is external or not and would skip the session activation/deactivation.
         if (isExternalCall) {
-            onCallRemoved(call);
+            handleCallRemoval();
         } else {
-            onCallAdded(call);
+            handleCallAddition();
         }
     }
+
+    @VisibleForTesting
+    /**
+     * @return the handler this class instance uses for operation; used for unit testing.
+     */
+    public Handler getHandler() {
+        return mMediaSessionHandler;
+    }
 }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 766cb12..ec87555 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -21,9 +21,13 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -37,6 +41,7 @@
 import android.content.pm.ServiceInfo;
 import android.hardware.SensorPrivacyManager;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -86,7 +91,6 @@
         AppOpsManager.OnOpActiveChangedListener {
     public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
     public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
-
     public class InCallServiceConnection {
         /**
          * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
@@ -616,24 +620,17 @@
                     getInCallServiceComponent(packageName,
                             IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabed */);
 
-            if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) {
+            if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)
+                    && carModeConnectionInfo != null) {
                 Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => "
                         + carModeConnectionInfo);
                 if (mIsConnected) {
                     mCurrentConnection.disconnect();
                 }
 
-                if (carModeConnectionInfo != null) {
-                    // Valid car mode app.
-                    mCarModeConnection = mCurrentConnection =
-                            new InCallServiceBindingConnection(carModeConnectionInfo);
-                    mIsCarMode = true;
-                } else {
-                    // The app is not enabled. Using the default dialer connection instead
-                    mCarModeConnection = null;
-                    mIsCarMode = false;
-                    mCurrentConnection = mDialerConnection;
-                }
+                mCarModeConnection = mCurrentConnection =
+                        new InCallServiceBindingConnection(carModeConnectionInfo);
+                mIsCarMode = true;
 
                 int result = mCurrentConnection.connect(null);
                 mIsConnected = result == CONNECTION_SUCCEEDED;
@@ -1625,25 +1622,31 @@
         mNonUIInCallServiceConnections.connect(call);
     }
 
-    private InCallServiceInfo getDefaultDialerComponent() {
-        String packageName = mDefaultDialerCache.getDefaultDialerApplication(
+    private @Nullable InCallServiceInfo getDefaultDialerComponent() {
+        String defaultPhoneAppName = mDefaultDialerCache.getDefaultDialerApplication(
                 mCallsManager.getCurrentUserHandle().getIdentifier());
-        String systemPackageName = mDefaultDialerCache.getSystemDialerApplication();
-        Log.d(this, "Default Dialer package: " + packageName);
+        String systemPhoneAppName = mDefaultDialerCache.getSystemDialerApplication();
 
-        InCallServiceInfo defaultDialerComponent =
-                (systemPackageName != null && systemPackageName.equals(packageName))
-                        ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI,
-                        true /* ignoreDisabled */)
-                        : getInCallServiceComponent(packageName,
+        Log.d(this, "getDefaultDialerComponent: defaultPhoneAppName=[%s]", defaultPhoneAppName);
+        Log.d(this, "getDefaultDialerComponent: systemPhoneAppName=[%s]", systemPhoneAppName);
+
+        // Get the defaultPhoneApp InCallService component...
+        InCallServiceInfo defaultPhoneAppComponent =
+                (systemPhoneAppName != null && systemPhoneAppName.equals(defaultPhoneAppName)) ?
+                        /* The defaultPhoneApp is also the systemPhoneApp. Get systemPhoneApp info*/
+                        getInCallServiceComponent(defaultPhoneAppName,
+                                IN_CALL_SERVICE_TYPE_SYSTEM_UI, true /* ignoreDisabled */)
+                        /* The defaultPhoneApp is NOT the systemPhoneApp. Get defaultPhoneApp info*/
+                        : getInCallServiceComponent(defaultPhoneAppName,
                                 IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */);
-        /* TODO: in Android 12 re-enable this an InCallService is required by the dialer role.
-            if (packageName != null && defaultDialerComponent == null) {
-                // The in call service of default phone app is disabled, send notification.
-                sendCrashedInCallServiceNotification(packageName);
-            }
-        */
-        return defaultDialerComponent;
+
+        Log.d(this, "getDefaultDialerComponent: defaultPhoneAppComponent=[%s]",
+                defaultPhoneAppComponent);
+
+        // defaultPhoneAppComponent is null in the case when the defaultPhoneApp does not implement
+        // the InCallService && is the package is different from the systemPhoneApp
+
+        return defaultPhoneAppComponent;
     }
 
     private InCallServiceInfo getCurrentCarModeComponent() {
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 524d119..4665ec2 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -33,6 +33,7 @@
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Play a call-related tone (ringback, busy signal, etc.) either through ToneGenerator, or using a
@@ -181,12 +182,15 @@
     private static final int STATE_ON = 1;
     private static final int STATE_STOPPED = 2;
 
+    // Invalid audio stream
+    private static final int STREAM_INVALID = -1;
+
     /**
      * Keeps count of the number of actively playing tones so that we can notify CallAudioManager
      * when we need focus and when it can be release. This should only be manipulated from the main
      * thread.
      */
-    private static int sTonesPlaying = 0;
+    private static AtomicInteger sTonesPlaying = new AtomicInteger(0);
 
     private final CallAudioManager mCallAudioManager;
     private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
@@ -213,6 +217,12 @@
     private final AudioManagerAdapter mAudioManagerAdapter;
 
     /**
+     * Latch used for awaiting on playback, which may be interrupted if the tone is stopped from
+     * outside the playback.
+     */
+    private final CountDownLatch mPlaybackLatch = new CountDownLatch(1);
+
+    /**
      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
      *
      * @param toneId ID of the tone to play, see TONE_* constants.
@@ -355,8 +365,14 @@
             if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
                 stream = AudioManager.STREAM_BLUETOOTH_SCO;
             }
-
             if (toneType != ToneGenerator.TONE_UNKNOWN) {
+                if (stream == AudioManager.STREAM_BLUETOOTH_SCO) {
+                    // Override audio stream for BT le device and hearing aid device
+                    if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn()
+                            || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) {
+                        stream = AudioManager.STREAM_VOICE_CALL;
+                    }
+                }
                 playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis);
             } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) {
                 playMediaTone(stream, mediaResourceId);
@@ -388,26 +404,21 @@
             }
 
             Log.i(this, "playToneGeneratorTone: toneType=%d", toneType);
-            // TODO: Certain CDMA tones need to check the ringer-volume state before
-            // playing. See CallNotifier.InCallTonePlayer.
 
-            // TODO: Some tones play through the end of a call so we need to inform
-            // CallAudioManager that we want focus the same way that Ringer does.
-
-            synchronized (this) {
-                if (mState != STATE_STOPPED) {
-                    mState = STATE_ON;
-                    toneGenerator.startTone(toneType);
-                    try {
-                        Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
-                                toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
-                        wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
-                    } catch (InterruptedException e) {
-                        Log.w(this, "wait interrupted", e);
-                    }
+            mState = STATE_ON;
+            toneGenerator.startTone(toneType);
+            try {
+                Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
+                        toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
+                if (mPlaybackLatch.await(toneLengthMillis + TIMEOUT_BUFFER_MILLIS,
+                        TimeUnit.MILLISECONDS)) {
+                    Log.i(this, "playToneGeneratorTone: tone playback stopped.");
                 }
+            } catch (InterruptedException e) {
+                Log.w(this, "playToneGeneratorTone: wait interrupted", e);
             }
-            mState = STATE_OFF;
+            // Redundant; don't want anyone re-using at this point.
+            mState = STATE_STOPPED;
         } finally {
             if (toneGenerator != null) {
                 toneGenerator.release();
@@ -421,42 +432,40 @@
      * @param toneResourceId The resource ID of the tone to play.
      */
     private void playMediaTone(int stream, int toneResourceId) {
-        synchronized (this) {
-            if (mState != STATE_STOPPED) {
-                mState = STATE_ON;
+        mState = STATE_ON;
+        Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+                .setLegacyStreamType(stream)
+                .build();
+        mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
+        mToneMediaPlayer.setLooping(false);
+        int durationMillis = mToneMediaPlayer.getDuration();
+        mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                Log.i(InCallTonePlayer.this, "playMediaTone: toneResourceId=%d completed.",
+                        toneResourceId);
+                mPlaybackLatch.countDown();
             }
-            Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
-            AudioAttributes attributes = new AudioAttributes.Builder()
-                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
-                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-                    .setLegacyStreamType(stream)
-                    .build();
-            mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
-            mToneMediaPlayer.setLooping(false);
-            int durationMillis = mToneMediaPlayer.getDuration();
-            final CountDownLatch toneLatch = new CountDownLatch(1);
-            mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-                @Override
-                public void onCompletion(MediaPlayer mp) {
-                    Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
-                    synchronized (InCallTonePlayer.this) {
-                        mState = STATE_OFF;
-                    }
-                    mToneMediaPlayer.release();
-                    mToneMediaPlayer = null;
-                    toneLatch.countDown();
-                }
-            });
-            mToneMediaPlayer.start();
-            try {
-                // Wait for the tone to stop playing; timeout at 2x the length of the file just to
-                // be on the safe side.
-                toneLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException ie) {
-                Log.e(this, ie, "playMediaTone: tone playback interrupted.");
-            }
-        }
+        });
 
+        try {
+            mToneMediaPlayer.start();
+            // Wait for the tone to stop playing; timeout at 2x the length of the file just to
+            // be on the safe side.  Playback can also be stopped via stopTone().
+            if (mPlaybackLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS)) {
+                Log.i(this, "playMediaTone: tone playback stopped.");
+            }
+        } catch (InterruptedException ie) {
+            Log.e(this, ie, "playMediaTone: tone playback interrupted.");
+        } finally {
+            // Redundant; don't want anyone re-using at this point.
+            mState = STATE_STOPPED;
+            mToneMediaPlayer.release();
+            mToneMediaPlayer = null;
+        }
     }
 
     @VisibleForTesting
@@ -467,8 +476,12 @@
             return false;
         }
 
-        sTonesPlaying++;
-        if (sTonesPlaying == 1) {
+        // Tone already done; don't allow re-used
+        if (mState == STATE_STOPPED) {
+            return false;
+        }
+
+        if (sTonesPlaying.incrementAndGet() == 1) {
             mCallAudioManager.setIsTonePlaying(true);
         }
 
@@ -493,29 +506,38 @@
      */
     @VisibleForTesting
     public void stopTone() {
-        synchronized (this) {
-            if (mState == STATE_ON) {
-                Log.d(this, "Stopping the tone %d.", mToneId);
-                notify();
-            }
-            mState = STATE_STOPPED;
-        }
+        Log.i(this, "stopTone: Stopping the tone %d.", mToneId);
+        // Notify the playback to end early.
+        mPlaybackLatch.countDown();
+
+        mState = STATE_STOPPED;
     }
 
     @VisibleForTesting
     public void cleanup() {
-        sTonesPlaying = 0;
+        sTonesPlaying.set(0);
     }
 
     private void cleanUpTonePlayer() {
+        Log.d(this, "cleanUpTonePlayer(): posting cleanup");
         // Release focus on the main thread.
         mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) {
             @Override
             public void loggedRun() {
-                if (sTonesPlaying == 0) {
-                    Log.wtf(this, "Over-releasing focus for tone player.");
-                } else if (--sTonesPlaying == 0 && mCallAudioManager != null) {
-                    mCallAudioManager.setIsTonePlaying(false);
+                int newToneCount = sTonesPlaying.updateAndGet( t -> Math.min(0, t--));
+
+                if (newToneCount == 0) {
+                    Log.i(InCallTonePlayer.this,
+                            "cleanUpTonePlayer(): tonesPlaying=%d, tone completed", newToneCount);
+                    if (mCallAudioManager != null) {
+                        mCallAudioManager.setIsTonePlaying(false);
+                    } else {
+                        Log.w(InCallTonePlayer.this,
+                                "cleanUpTonePlayer(): mCallAudioManager is null!");
+                    }
+                } else {
+                    Log.i(InCallTonePlayer.this,
+                            "cleanUpTonePlayer(): tonesPlaying=%d; still playing", newToneCount);
                 }
             }
         }.prepare());
diff --git a/src/com/android/server/telecom/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index b37883d..bc555e2 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -60,10 +60,18 @@
         handleWakeLock();
     }
 
+    @Override
+    public void onExternalCallChanged(Call call, boolean isExternalCall) {
+        // In case a call changes its external call state during ringing, we need to trigger
+        // the wake lock update correspondingly. External call is handled by another device
+        // and should not hold a wake lock on the local device.
+        handleWakeLock();
+    }
+
     private void handleWakeLock() {
         // We grab a full lock as long as there exists a ringing call.
         Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
-        if (ringingCall != null) {
+        if (ringingCall != null && !ringingCall.isExternalCall()) {
             mTelecomWakeLock.acquire();
             Log.i(this, "Acquiring full wake lock");
         } else {
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index f53f239..12e780f 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -21,6 +21,8 @@
 import android.telecom.Logging.EventManager;
 import android.telecom.Logging.EventManager.TimedEventPair;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -34,6 +36,9 @@
     private static final String LOGUTILS_TAG = "LogUtils";
 
     public static final boolean SYSTRACE_DEBUG = false; /* STOP SHIP if true */
+    private static boolean sInitializedLoggerListeners = false; // used to gate re-init of listeners
+    private static int sInitializedCounter = 0; /* For testing purposes only */
+    private static final Object sLock = new Object(); // Coarse lock for all of LogUtils
 
     public static class EventTimer {
         private long mLastElapsedMillis;
@@ -203,6 +208,7 @@
         public static final String CALL_DIAGNOSTIC_SERVICE_TIMEOUT =
                 "CALL_DIAGNOSTIC_SERVICE_TIMEOUT";
         public static final String VERSTAT_CHANGED = "VERSTAT_CHANGED";
+        public static final String SET_VOIP_MODE = "SET_VOIP_MODE";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
@@ -250,12 +256,13 @@
         EventManager.Loggable recordEntry = eventRecord.getRecordEntry();
         if (recordEntry instanceof Call) {
             Call callRecordEntry = (Call) recordEntry;
-            android.telecom.Log.i(LOGUTILS_TAG, "EventRecord added as Call: " + callRecordEntry);
             Analytics.CallInfo callInfo = callRecordEntry.getAnalytics();
             if(callInfo != null) {
                 callInfo.setCallEvents(eventRecord);
             } else {
-                android.telecom.Log.w(LOGUTILS_TAG, "Could not get Analytics CallInfo.");
+                if(!android.telecom.Log.isUnitTestingEnabled()) {
+                    android.telecom.Log.w(LOGUTILS_TAG, "Could not get Analytics CallInfo.");
+                }
             }
         } else {
             android.telecom.Log.w(LOGUTILS_TAG, "Non-Call EventRecord Added.");
@@ -263,13 +270,41 @@
     }
 
     public static void initLogging(Context context) {
-        android.telecom.Log.setTag(TAG);
-        android.telecom.Log.setSessionContext(context);
-        for (EventManager.TimedEventPair p : Events.Timings.sTimedEvents) {
-            android.telecom.Log.addRequestResponsePair(p);
+        android.telecom.Log.d(LOGUTILS_TAG, "initLogging: attempting to acquire LogUtils sLock");
+        synchronized (sLock) {
+            android.telecom.Log.d(LOGUTILS_TAG, "initLogging: grabbed LogUtils sLock");
+            if (!sInitializedLoggerListeners) {
+                android.telecom.Log.d(LOGUTILS_TAG,
+                        "initLogging: called for first time. Registering the EventListener &"
+                                + " SessionListener.");
+
+                android.telecom.Log.setTag(TAG);
+                android.telecom.Log.setSessionContext(context);
+                for (EventManager.TimedEventPair p : Events.Timings.sTimedEvents) {
+                    android.telecom.Log.addRequestResponsePair(p);
+                }
+                android.telecom.Log.registerEventListener(LogUtils::eventRecordAdded);
+                // Store analytics about recently completed Sessions.
+                android.telecom.Log.registerSessionListener(Analytics::addSessionTiming);
+
+                // Ensure LogUtils#initLogging(Context) is called once throughout the entire
+                // lifecycle of not only TelecomSystem, but the Testing Framework.
+                sInitializedLoggerListeners = true;
+                sInitializedCounter++; /* For testing purposes only */
+            } else {
+                android.telecom.Log.d(LOGUTILS_TAG, "initLogging: called again. Doing nothing.");
+            }
         }
-        android.telecom.Log.registerEventListener(LogUtils::eventRecordAdded);
-        // Store analytics about recently completed Sessions.
-        android.telecom.Log.registerSessionListener(Analytics::addSessionTiming);
+    }
+
+    /**
+     * Needed in order to test if the registerEventListener & registerSessionListener are ever
+     * re-initialized in the entire process of the Testing Framework or TelecomSystem.
+     *
+     * @return the number of times initLogging(Context) listeners have been initialized
+     */
+    @VisibleForTesting
+    public static int getInitializedCounter() {
+        return sInitializedCounter;
     }
 }
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 4e8524e..c566e5a 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -19,9 +19,11 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -45,6 +47,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -132,6 +135,26 @@
                 PhoneAccount phoneAccount) {}
     }
 
+    /**
+     * Receiver for detecting when a managed profile has been removed so that PhoneAccountRegistrar
+     * can clean up orphan {@link PhoneAccount}s
+     */
+    private final BroadcastReceiver mManagedProfileReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.startSession("PARbR.oR");
+            try {
+                synchronized (mLock) {
+                    if (intent.getAction().equals(Intent.ACTION_MANAGED_PROFILE_REMOVED)) {
+                        cleanupOrphanedPhoneAccounts();
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    };
+
     public static final String FILE_NAME = "phone-account-registrar-state.xml";
     @VisibleForTesting
     public static final int EXPECTED_STATE_VERSION = 9;
@@ -144,9 +167,11 @@
     private final AtomicFile mAtomicFile;
     private final Context mContext;
     private final UserManager mUserManager;
+    private final TelephonyManager mTelephonyManager;
     private final SubscriptionManager mSubscriptionManager;
     private final DefaultDialerCache mDefaultDialerCache;
     private final AppLabelProxy mAppLabelProxy;
+    private final TelecomSystem.SyncRoot mLock;
     private State mState;
     private UserHandle mCurrentUserHandle;
     private String mTestPhoneAccountPackageNameFilter;
@@ -155,24 +180,31 @@
             new PhoneAccountRegistrarWriteLock() {};
 
     @VisibleForTesting
-    public PhoneAccountRegistrar(Context context, DefaultDialerCache defaultDialerCache,
-                                 AppLabelProxy appLabelProxy) {
-        this(context, FILE_NAME, defaultDialerCache, appLabelProxy);
+    public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock,
+            DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
+        this(context, lock, FILE_NAME, defaultDialerCache, appLabelProxy);
     }
 
     @VisibleForTesting
-    public PhoneAccountRegistrar(Context context, String fileName,
+    public PhoneAccountRegistrar(Context context, TelecomSystem.SyncRoot lock, String fileName,
             DefaultDialerCache defaultDialerCache, AppLabelProxy appLabelProxy) {
 
         mAtomicFile = new AtomicFile(new File(context.getFilesDir(), fileName));
 
         mState = new State();
         mContext = context;
+        mLock = lock;
         mUserManager = UserManager.get(context);
         mDefaultDialerCache = defaultDialerCache;
         mSubscriptionManager = SubscriptionManager.from(mContext);
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppLabelProxy = appLabelProxy;
         mCurrentUserHandle = Process.myUserHandle();
+
+        // register context based receiver to clean up orphan phone accounts
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+        mContext.registerReceiver(mManagedProfileReceiver, intentFilter);
+
         read();
     }
 
@@ -188,9 +220,7 @@
         PhoneAccount account = getPhoneAccountUnchecked(accountHandle);
 
         if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
-            TelephonyManager tm =
-                    (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-            return tm.getSubscriptionId(accountHandle);
+            return mTelephonyManager.getSubscriptionId(accountHandle);
         }
         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
@@ -336,11 +366,11 @@
                 isSimAccount = true;
             }
 
-            Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
             mState.defaultOutgoingAccountHandles
                     .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle,
                             account.getGroupId()));
         }
+        Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
 
         // Potentially update the default voice subid in SubscriptionManager.
         if (!Objects.equals(currentDefaultPhoneAccount, accountHandle)) {
@@ -454,6 +484,31 @@
     }
 
     /**
+     * Loops through all SIM accounts ({@link #getSimPhoneAccounts}) and returns those with SIM call
+     * manager components specified in carrier config that match {@code simCallManagerHandle}.
+     *
+     * <p>Note that this will return handles even when {@code simCallManagerHandle} has not yet been
+     * registered or was recently unregistered.
+     *
+     * <p>If the given {@code simCallManagerHandle} is not the SIM call manager for any active SIMs,
+     * returns an empty list.
+     */
+    public @NonNull List<PhoneAccountHandle> getSimPhoneAccountsFromSimCallManager(
+            @NonNull PhoneAccountHandle simCallManagerHandle) {
+        List<PhoneAccountHandle> matchingSimHandles = new ArrayList<>();
+        for (PhoneAccountHandle simHandle :
+                getSimPhoneAccounts(simCallManagerHandle.getUserHandle())) {
+            ComponentName simCallManager =
+                    getSystemSimCallManagerComponent(getSubscriptionIdForPhoneAccount(simHandle));
+            if (simCallManager == null) continue;
+            if (simCallManager.equals(simCallManagerHandle.getComponentName())) {
+                matchingSimHandles.add(simHandle);
+            }
+        }
+        return matchingSimHandles;
+    }
+
+    /**
      * Sets a filter for which {@link PhoneAccount}s will be returned from
      * {@link #filterRestrictedPhoneAccounts(List)}. If non-null, only {@link PhoneAccount}s
      * with the package name packageNameFilter will be returned. If null, no filter is set.
@@ -754,6 +809,25 @@
     }
 
     /**
+     * Retrieves a list of all {@link PhoneAccount#CAPABILITY_SELF_MANAGED} phone accounts
+     * registered by a specified package.
+     *
+     * @param packageName The name of the package that registered the phone accounts.
+     * @return The self-managed phone account handles for the given package.
+     */
+    public List<PhoneAccountHandle> getSelfManagedPhoneAccountsForPackage(String packageName,
+            UserHandle userHandle) {
+        List<PhoneAccountHandle> phoneAccountsHandles = new ArrayList<>();
+        for (PhoneAccountHandle pah : getPhoneAccountsForPackage(packageName,
+                userHandle)) {
+            if (isSelfManagedPhoneAccount(pah)) {
+                phoneAccountsHandles.add(pah);
+            }
+        }
+        return phoneAccountsHandles;
+    }
+
+    /**
      * Determines if a {@link PhoneAccountHandle} is for a self-managed {@link ConnectionService}.
      * @param handle The handle.
      * @return {@code true} if for a self-managed {@link ConnectionService}, {@code false}
@@ -866,6 +940,9 @@
         } else {
             fireAccountChanged(account);
         }
+        // If this is the SIM call manager, tell telephony when the voice ServiceState override
+        // needs to be updated.
+        maybeNotifyTelephonyForVoiceServiceState(account, /* registered= */ true);
     }
 
     public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
@@ -875,6 +952,9 @@
                 write();
                 fireAccountsChanged();
                 fireAccountUnRegistered(accountHandle);
+                // If this is the SIM call manager, tell telephony when the voice ServiceState
+                // override needs to be updated.
+                maybeNotifyTelephonyForVoiceServiceState(account, /* registered= */ false);
             }
         }
     }
@@ -1017,6 +1097,71 @@
         }
     }
 
+    private void maybeNotifyTelephonyForVoiceServiceState(
+            @NonNull PhoneAccount account, boolean registered) {
+        // TODO(b/215419665) what about SIM_SUBSCRIPTION accounts? They could theoretically also use
+        // these capabilities, but don't today. If they do start using them, then there will need to
+        // be a kind of "or" logic between SIM_SUBSCRIPTION and CONNECTION_MANAGER accounts to get
+        // the correct value of hasService for a given SIM.
+        boolean hasService = false;
+        List<PhoneAccountHandle> simHandlesToNotify;
+        if (account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
+            // When we unregister the SIM call manager account, we always set hasService back to
+            // false since it is no longer providing OTT calling capability once unregistered.
+            if (registered) {
+                // Note: we do *not* early return when the SUPPORTS capability is not present
+                // because it's possible the SIM call manager could remove either capability at
+                // runtime and re-register. However, it is an error to use the AVAILABLE capability
+                // without also setting SUPPORTS.
+                hasService =
+                        account.hasCapabilities(
+                                PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS
+                                        | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE);
+            }
+            // Notify for all SIMs that named this component as their SIM call manager in carrier
+            // config, since there may be more than one impacted SIM here.
+            simHandlesToNotify = getSimPhoneAccountsFromSimCallManager(account.getAccountHandle());
+        } else if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+            // When new SIMs get registered, we notify them of their current voice status override.
+            // If there is no SIM call manager for this SIM, we treat that as hasService = false and
+            // still notify to ensure consistency.
+            if (!registered) {
+                // We don't do anything when SIMs are unregistered because we won't have an active
+                // subId to map back to phoneId and tell telephony about; that case is handled by
+                // telephony internally.
+                return;
+            }
+            PhoneAccountHandle simCallManagerHandle =
+                    getSimCallManagerFromHandle(
+                            account.getAccountHandle(), account.getAccountHandle().getUserHandle());
+            if (simCallManagerHandle != null) {
+                PhoneAccount simCallManager = getPhoneAccountUnchecked(simCallManagerHandle);
+                hasService =
+                        simCallManager != null
+                                && simCallManager.hasCapabilities(
+                                        PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS
+                                                | PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE);
+            }
+            simHandlesToNotify = Collections.singletonList(account.getAccountHandle());
+        } else {
+            // Not a relevant account - we only care about CONNECTION_MANAGER and SIM_SUBSCRIPTION.
+            return;
+        }
+        if (simHandlesToNotify.isEmpty()) return;
+        Log.i(
+                this,
+                "Notifying telephony of voice service override change for %d SIMs, hasService = %b",
+                simHandlesToNotify.size(),
+                hasService);
+        for (PhoneAccountHandle simHandle : simHandlesToNotify) {
+            // This may be null if there are no active SIMs but the device is still camped for
+            // emergency calls and registered a SIM_SUBSCRIPTION for that purpose.
+            TelephonyManager simTm = mTelephonyManager.createForPhoneAccountHandle(simHandle);
+            if (simTm == null) continue;
+            simTm.setVoiceServiceStateOverride(hasService);
+        }
+    }
+
     /**
      * Determines if the connection service specified by a {@link PhoneAccountHandle} requires the
      * {@link Manifest.permission#BIND_TELECOM_CONNECTION_SERVICE} permission.
@@ -1269,7 +1414,7 @@
 
         public final UserHandle userHandle;
 
-        public final PhoneAccountHandle phoneAccountHandle;
+        public PhoneAccountHandle phoneAccountHandle;
 
         public final String groupId;
 
@@ -1388,8 +1533,7 @@
         try {
             sortPhoneAccounts();
             ByteArrayOutputStream os = new ByteArrayOutputStream();
-            XmlSerializer serializer = new FastXmlSerializer();
-            serializer.setOutput(os, "utf-8");
+            XmlSerializer serializer = Xml.resolveSerializer(os);
             writeToXml(mState, serializer, mContext);
             serializer.flush();
             new AsyncXmlWriter().execute(os);
@@ -1408,12 +1552,11 @@
 
         boolean versionChanged = false;
 
-        XmlPullParser parser;
         try {
-            parser = Xml.newPullParser();
-            parser.setInput(new BufferedInputStream(is), null);
+            XmlPullParser parser = Xml.resolvePullParser(is);
             parser.nextTag();
             mState = readFromXml(parser, mContext);
+            migratePhoneAccountHandle(mState);
             versionChanged = mState.versionNumber < EXPECTED_STATE_VERSION;
 
         } catch (IOException | XmlPullParserException e) {
@@ -1458,6 +1601,50 @@
         return s != null ? s : new State();
     }
 
+    /**
+     * Try to migrate the ID of default phone account handle from IccId to SubId.
+     */
+    @VisibleForTesting
+    public void migratePhoneAccountHandle(State state) {
+        if (mSubscriptionManager == null) {
+            return;
+        }
+        // Use getAllSubscirptionInfoList() to get the mapping between iccId and subId
+        // from the subscription database
+        List<SubscriptionInfo> subscriptionInfos = mSubscriptionManager
+                .getAllSubscriptionInfoList();
+        Map<UserHandle, DefaultPhoneAccountHandle> defaultPhoneAccountHandles
+                = state.defaultOutgoingAccountHandles;
+        for (Map.Entry<UserHandle, DefaultPhoneAccountHandle> entry
+                : defaultPhoneAccountHandles.entrySet()) {
+            DefaultPhoneAccountHandle defaultPhoneAccountHandle = entry.getValue();
+
+            // Migrate Telephony PhoneAccountHandle only
+            String telephonyComponentName =
+                    "com.android.phone/com.android.services.telephony.TelephonyConnectionService";
+            if (!defaultPhoneAccountHandle.phoneAccountHandle.getComponentName()
+                    .flattenToString().equals(telephonyComponentName)) {
+                continue;
+            }
+            // Migrate from IccId to SubId
+            for (SubscriptionInfo subscriptionInfo : subscriptionInfos) {
+                String phoneAccountHandleId = defaultPhoneAccountHandle.phoneAccountHandle.getId();
+                // Some phone account handle would store phone account handle id with the IccId
+                // string plus "F", and the getIccId() returns IccId string itself without "F",
+                // so here need to use "startsWith" to match.
+                if (phoneAccountHandleId != null && phoneAccountHandleId.startsWith(
+                        subscriptionInfo.getIccId())) {
+                    Log.i(this, "Found subscription ID to migrate: "
+                            + subscriptionInfo.getSubscriptionId());
+                    defaultPhoneAccountHandle.phoneAccountHandle = new PhoneAccountHandle(
+                            defaultPhoneAccountHandle.phoneAccountHandle.getComponentName(),
+                                    Integer.toString(subscriptionInfo.getSubscriptionId()));
+                    break;
+                }
+            }
+        }
+    }
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
     //
     // XML serialization
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 6c9b1cc..b29b500 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -16,11 +16,14 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.USER_MISSED_DND_MODE;
+import static android.provider.CallLog.Calls.USER_MISSED_LOW_RING_VOLUME;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
+
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.Person;
 import android.content.Context;
-import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.VolumeShaper;
@@ -28,6 +31,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.telecom.Log;
@@ -38,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -59,6 +64,9 @@
     @VisibleForTesting
     public VibrationEffect mDefaultVibrationEffect;
 
+    // Used for test to notify the completion of RingerAttributes
+    private CountDownLatch mAttributesLatch;
+
     private static final long[] PULSE_PRIMING_PATTERN = {0,12,250,12,500}; // priming  + interval
 
     private static final int[] PULSE_PRIMING_AMPLITUDE = {0,255,0,255,0};  // priming  + interval
@@ -119,12 +127,9 @@
 
     private static final float EPSILON = 1e-6f;
 
-    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
-            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-            .build();
+    private static final VibrationAttributes VIBRATION_ATTRIBUTES =
+            new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_RINGTONE).build();
 
-    private static VibrationEffect mRampingRingerVibrationEffect;
     private static VolumeShaper.Configuration mVolumeShaperConfig;
 
     /**
@@ -167,6 +172,12 @@
 
     private Handler mHandler = null;
 
+    /**
+     * Use lock different from the Telecom sync because ringing process is asynchronous outside that
+     * lock
+     */
+    private final Object mLock;
+
     /** Initializes the Ringer. */
     @VisibleForTesting
     public Ringer(
@@ -179,6 +190,7 @@
             VibrationEffectProxy vibrationEffectProxy,
             InCallController inCallController) {
 
+        mLock = new Object();
         mSystemSettingsUtil = systemSettingsUtil;
         mPlayerFactory = playerFactory;
         mContext = context;
@@ -189,7 +201,6 @@
         mRingtoneFactory = ringtoneFactory;
         mInCallController = inCallController;
         mVibrationEffectProxy = vibrationEffectProxy;
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
 
         if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
             mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -232,6 +243,7 @@
 
         RingerAttributes attributes = null;
         try {
+            mAttributesLatch = new CountDownLatch(1);
             attributes = ringerAttributesFuture.get(
                     RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
         } catch (ExecutionException | InterruptedException | TimeoutException e) {
@@ -263,7 +275,9 @@
         VibrationEffect effect;
         CompletableFuture<Boolean> hapticsFuture = null;
         // Determine if the settings and DND mode indicate that the vibrator can be used right now.
-        boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall);
+        boolean isVibratorEnabled = isVibratorEnabled(mContext);
+        boolean shouldApplyRampingRinger =
+                isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
         if (attributes.isRingerAudible()) {
             mRingingCall = foregroundCall;
             Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
@@ -271,9 +285,9 @@
             // 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.
-            if (mSystemSettingsUtil.applyRampingRinger(mContext)) {
+            if (shouldApplyRampingRinger) {
                 Log.i(this, "start ramping ringer.");
-                if (mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
+                if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
                     effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
                 } else {
                     effect = mDefaultVibrationEffect;
@@ -319,8 +333,8 @@
                             isUsingAudioCoupledHaptics, mIsHapticPlaybackSupportedByDevice);
                     maybeStartVibration(foregroundCall, shouldRingForContact, effect,
                             isVibratorEnabled, isRingerAudible);
-                } else if (mSystemSettingsUtil.applyRampingRinger(mContext)
-                           && !mSystemSettingsUtil.enableAudioCoupledVibrationForRampingRinger()) {
+                } else if (shouldApplyRampingRinger
+                        && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
                     Log.i(this, "startRinging: apply ramping ringer vibration");
                     maybeStartVibration(foregroundCall, shouldRingForContact, effect,
                             isVibratorEnabled, isRingerAudible);
@@ -346,20 +360,29 @@
 
     private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact,
         VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible) {
-        if (isVibrationEnabled
-                && !mIsVibrating && shouldRingForContact) {
-            if (mSystemSettingsUtil.applyRampingRinger(mContext)
-                    && isRingerAudible) {
-                Log.i(this, "start vibration for ramping ringer.");
+        synchronized (mLock) {
+            mAudioManager = mContext.getSystemService(AudioManager.class);
+            if (isVibrationEnabled && !mIsVibrating && shouldRingForContact) {
+                Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
+                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+                        mVibrator.hasVibrator(),
+                        mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+                        mAudioManager.getRingerModeInternal(), mIsVibrating);
+                if (mSystemSettingsUtil.isRampingRingerEnabled(mContext) && isRingerAudible) {
+                    Log.i(this, "start vibration for ramping ringer.");
+                } else {
+                    Log.i(this, "start normal vibration.");
+                }
                 mIsVibrating = true;
                 mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
             } else {
-                Log.i(this, "start normal vibration.");
-                mIsVibrating = true;
-                mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
+                foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
+                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+                        mVibrator.hasVibrator(),
+                        mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+                        mAudioManager.getRingerModeInternal(), mIsVibrating);
             }
-        } else if (mIsVibrating) {
-            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION, "already vibrating");
         }
     }
 
@@ -419,24 +442,26 @@
     }
 
     public void stopRinging() {
-        if (mRingingCall != null) {
-            Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
-            mRingingCall = null;
-        }
+        synchronized (mLock) {
+            if (mRingingCall != null) {
+                Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
+                mRingingCall = null;
+            }
 
-        mRingtonePlayer.stop();
+            mRingtonePlayer.stop();
 
-        // If we haven't started vibrating because we were waiting for the haptics info, cancel
-        // it and don't vibrate at all.
-        if (mVibrateFuture != null) {
-            mVibrateFuture.cancel(true);
-        }
+            // If we haven't started vibrating because we were waiting for the haptics info, cancel
+            // it and don't vibrate at all.
+            if (mVibrateFuture != null) {
+                mVibrateFuture.cancel(true);
+            }
 
-        if (mIsVibrating) {
-            Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
-            mVibrator.cancel();
-            mIsVibrating = false;
-            mVibratingCall = null;
+            if (mIsVibrating) {
+                Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
+                mVibrator.cancel();
+                mIsVibrating = false;
+                mVibratingCall = null;
+            }
         }
     }
 
@@ -472,48 +497,21 @@
     private boolean hasExternalRinger(Call foregroundCall) {
         Bundle intentExtras = foregroundCall.getIntentExtras();
         if (intentExtras != null) {
-            return intentExtras.getBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, false);
+            return intentExtras.getBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, false);
         } else {
             return false;
         }
     }
 
-    private boolean isVibratorEnabled(Context context, Call call) {
+    private boolean isVibratorEnabled(Context context) {
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-        int ringerMode = audioManager.getRingerModeInternal();
-        boolean shouldVibrate;
-        if (getVibrateWhenRinging(context)) {
-            shouldVibrate = ringerMode != AudioManager.RINGER_MODE_SILENT;
-        } else {
-            shouldVibrate = ringerMode == AudioManager.RINGER_MODE_VIBRATE;
-        }
-
-        // Technically this should be in the calling method, but it seemed a little odd to pass
-        // around a whole bunch of state just for logging purposes.
-        if (shouldVibrate) {
-            Log.addEvent(call, LogUtils.Events.START_VIBRATOR,
-                    "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
-                    mVibrator.hasVibrator(), mSystemSettingsUtil.canVibrateWhenRinging(context),
-                    ringerMode, mIsVibrating);
-        } else {
-            Log.addEvent(call, LogUtils.Events.SKIP_VIBRATION,
-                    "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
-                    mVibrator.hasVibrator(), mSystemSettingsUtil.canVibrateWhenRinging(context),
-                    ringerMode, mIsVibrating);
-        }
-
-        return shouldVibrate;
-    }
-
-    private boolean getVibrateWhenRinging(Context context) {
-        if (!mVibrator.hasVibrator()) {
-            return false;
-        }
-        return mSystemSettingsUtil.canVibrateWhenRinging(context)
-            || mSystemSettingsUtil.applyRampingRinger(context);
+        return mVibrator.hasVibrator()
+                && mSystemSettingsUtil.isRingVibrationEnabled(context)
+                && audioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_SILENT;
     }
 
     private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
+        mAudioManager = mContext.getSystemService(AudioManager.class);
         RingerAttributes.Builder builder = new RingerAttributes.Builder();
 
         LogUtils.EventTimer timer = new EventTimer();
@@ -565,6 +563,15 @@
         boolean shouldAcquireAudioFocus =
                 isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
 
+        // Set missed reason according to attributes
+        if (!isVolumeOverZero) {
+            call.setUserMissed(USER_MISSED_LOW_RING_VOLUME);
+        }
+        if (!shouldRingForContact) {
+            call.setUserMissed(USER_MISSED_DND_MODE);
+        }
+
+        mAttributesLatch.countDown();
         return builder.setEndEarly(endEarly)
                 .setLetDialerHandleRinging(letDialerHandleRinging)
                 .setAcquireAudioFocus(shouldAcquireAudioFocus)
@@ -583,4 +590,13 @@
         }
         return mHandler;
     }
+
+    @VisibleForTesting
+    public boolean waitForAttributesCompletion() throws InterruptedException {
+        if (mAttributesLatch != null) {
+            return mAttributesLatch.await(RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
+        } else {
+            return false;
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
index 7baae05..cdd14df 100644
--- a/src/com/android/server/telecom/SystemSettingsUtil.java
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -18,9 +18,10 @@
 
 import android.content.Context;
 import android.media.AudioManager;
+import android.os.VibrationAttributes;
+import android.os.Vibrator;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
-import android.telecom.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -39,9 +40,16 @@
                 0) == 1;
     }
 
-    public boolean canVibrateWhenRinging(Context context) {
+    public boolean isRingVibrationEnabled(Context context) {
+        // VIBRATE_WHEN_RINGING setting was deprecated, only RING_VIBRATION_INTENSITY controls the
+        // ringtone vibrations on/off state now. Ramping ringer should only be applied when ring
+        // vibration intensity is ON, otherwise the ringtone sound should not be delayed as there
+        // will be no ring vibration.
         return Settings.System.getIntForUser(context.getContentResolver(),
-                Settings.System.VIBRATE_WHEN_RINGING, 0, context.getUserId()) != 0;
+                Settings.System.RING_VIBRATION_INTENSITY,
+                context.getSystemService(Vibrator.class).getDefaultVibrationIntensity(
+                        VibrationAttributes.USAGE_RINGTONE),
+                context.getUserId()) != Vibrator.VIBRATION_INTENSITY_OFF;
     }
 
     public boolean isEnhancedCallBlockingEnabled(Context context) {
@@ -55,12 +63,11 @@
                 context.getUserId());
     }
 
-    public boolean applyRampingRinger(Context context) {
-        return Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.APPLY_RAMPING_RINGER, 0) == 1;
+    public boolean isRampingRingerEnabled(Context context) {
+        return context.getSystemService(AudioManager.class).isRampingRingerEnabled();
     }
 
-    public boolean enableAudioCoupledVibrationForRampingRinger() {
+    public boolean isAudioCoupledVibrationForRampingRingerEnabled() {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
                 RAMPING_RINGER_AUDIO_COUPLED_VIBRATION_ENABLED, false);
     }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index c765a6e..06a190b 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -26,6 +26,7 @@
 import static android.Manifest.permission.READ_SMS;
 import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -249,6 +250,42 @@
         }
 
         @Override
+        public List<PhoneAccountHandle> getOwnSelfManagedPhoneAccounts(String callingPackage,
+                String callingFeatureId) {
+            try {
+                Log.startSession("TSI.gOSMPA", Log.getPackageAbbreviation(callingPackage));
+                try {
+                    enforceCallingPackage(callingPackage, "getOwnSelfManagedPhoneAccounts");
+                }
+                catch(SecurityException se){
+                    EventLog.writeEvent(0x534e4554, "231986341", Binder.getCallingUid(),
+                            "getOwnSelfManagedPhoneAccounts: invalid calling package");
+                    throw se;
+                }
+                if (!canReadMangeOwnCalls("Requires MANAGE_OWN_CALLS permission.")) {
+                    throw new SecurityException("Requires MANAGE_OWN_CALLS permission.");
+                }
+                synchronized (mLock) {
+                    final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        return mPhoneAccountRegistrar.getSelfManagedPhoneAccountsForPackage(
+                                callingPackage,
+                                callingUserHandle);
+                    } catch (Exception e) {
+                        Log.e(this, e,
+                                "getSelfManagedPhoneAccountsForPackage");
+                        throw e;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme,
                 String callingPackage) {
             try {
@@ -501,12 +538,6 @@
             try {
                 Log.startSession("TSI.rPA");
                 synchronized (mLock) {
-                    if (!((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
-                                .isVoiceCapable()) {
-                        Log.w(this,
-                                "registerPhoneAccount not allowed on non-voice capable device.");
-                        return;
-                    }
                     try {
                         enforcePhoneAccountModificationForPackage(
                                 account.getAccountHandle().getComponentName().getPackageName());
@@ -532,6 +563,15 @@
                         if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
                             enforceRegisterMultiUser();
                         }
+                        // These capabilities are for SIM-based accounts only, so only the platform
+                        // and carrier-designated SIM call manager can register accounts with these
+                        // capabilities.
+                        if (account.hasCapabilities(
+                                        PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS)
+                                || account.hasCapabilities(
+                                        PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE)) {
+                            enforceRegisterVoiceCallingIndicationCapabilities(account);
+                        }
                         Bundle extras = account.getExtras();
                         if (extras != null
                                 && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
@@ -1714,10 +1754,14 @@
          * @see android.telecom.TelecomManager#isIncomingCallPermitted(PhoneAccountHandle)
          */
         @Override
-        public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+        public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle,
+                String callingPackage) {
+            Log.startSession("TSI.iICP");
             try {
-                Log.startSession("TSI.iICP");
+                enforceCallingPackage(callingPackage, "isIncomingCallPermitted");
+                enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage);
                 enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS);
+                enforceUserHandleMatchesCaller(phoneAccountHandle);
                 synchronized (mLock) {
                     long token = Binder.clearCallingIdentity();
                     try {
@@ -1735,10 +1779,14 @@
          * @see android.telecom.TelecomManager#isOutgoingCallPermitted(PhoneAccountHandle)
          */
         @Override
-        public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
+        public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle,
+                String callingPackage) {
+            Log.startSession("TSI.iOCP");
             try {
-                Log.startSession("TSI.iOCP");
+                enforceCallingPackage(callingPackage, "isOutgoingCallPermitted");
+                enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage);
                 enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS);
+                enforceUserHandleMatchesCaller(phoneAccountHandle);
                 synchronized (mLock) {
                     long token = Binder.clearCallingIdentity();
                     try {
@@ -2065,6 +2113,39 @@
                 Log.endSession();
             }
         }
+
+        /**
+         * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
+         * calls for a given {@code packageName} and {@code userHandle}.
+         *
+         * @param packageName the package name of the app to check calls for.
+         * @param userHandle the user handle on which to check for calls.
+         * @param callingPackage The caller's package name.
+         * @return {@code true} if there are ongoing calls, {@code false} otherwise.
+         */
+        @Override
+        public boolean isInSelfManagedCall(String packageName, UserHandle userHandle,
+                String callingPackage) {
+            try {
+                if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+                    throw new SecurityException("Only the system can call this API");
+                }
+                mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE,
+                        "READ_PRIVILEGED_PHONE_STATE required.");
+
+                Log.startSession("TSI.iISMC", Log.getPackageAbbreviation(callingPackage));
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        return mCallsManager.isInSelfManagedCall(packageName, userHandle);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
     };
 
     /**
@@ -2277,7 +2358,7 @@
         if (result != PackageManager.PERMISSION_GRANTED) {
             // Other callers are only allowed to modify PhoneAccounts if the relevant system
             // feature is enabled ...
-            enforceConnectionServiceFeature();
+            enforceTelecomFeature();
             // ... and the PhoneAccounts they refer to are for their own package.
             enforceCallingPackage(packageName, "enforcePhoneAccountModificationForPackage");
         }
@@ -2313,8 +2394,13 @@
         }
     }
 
-    private void enforceConnectionServiceFeature() {
-        enforceFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+    private void enforceTelecomFeature() {
+        PackageManager pm = mContext.getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) {
+            throw new UnsupportedOperationException(
+                    "System does not support feature " + PackageManager.FEATURE_TELECOM);
+        }
     }
 
     private void enforceRegisterSimSubscriptionPermission() {
@@ -2343,6 +2429,24 @@
         }
     }
 
+    private void enforceRegisterVoiceCallingIndicationCapabilities(PhoneAccount account) {
+        // Caller must be able to register a SIM PhoneAccount or be the SIM call manager (as named
+        // in carrier config) to declare the two voice indication capabilities.
+        boolean prerequisiteCapabilitiesOk =
+                account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                        || account.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER);
+        boolean permissionsOk =
+                isCallerSimCallManagerForAnySim(account.getAccountHandle())
+                        || mContext.checkCallingOrSelfPermission(REGISTER_SIM_SUBSCRIPTION)
+                                == PackageManager.PERMISSION_GRANTED;
+        if (!prerequisiteCapabilitiesOk || !permissionsOk) {
+            throw new SecurityException(
+                    "Only SIM subscriptions and connection managers are allowed to declare "
+                            + "CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS and "
+                            + "CAPABILITY_VOICE_CALLING_AVAILABLE");
+        }
+    }
+
     private void enforceRegisterSkipCallFiltering() {
         if (!isCallerSystemApp()) {
             throw new SecurityException(
@@ -2356,6 +2460,13 @@
         }
     }
 
+    private void enforcePhoneAccountHandleMatchesCaller(PhoneAccountHandle phoneAccountHandle,
+            String callingPackage) {
+        if (!callingPackage.equals(phoneAccountHandle.getComponentName().getPackageName())) {
+            throw new SecurityException("Caller does not own the PhoneAccountHandle");
+        }
+    }
+
     private void enforceCrossUserPermission(int callingUid) {
         if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
             mContext.enforceCallingOrSelfPermission(
@@ -2364,14 +2475,6 @@
         }
     }
 
-    private void enforceFeature(String feature) {
-        PackageManager pm = mContext.getPackageManager();
-        if (!pm.hasSystemFeature(feature)) {
-            throw new UnsupportedOperationException(
-                    "System does not support feature " + feature);
-        }
-    }
-
     // to be used for TestApi methods that can only be called with SHELL UID.
     private void enforceShellOnly(int callingUid, String message) {
         if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
@@ -2404,6 +2507,15 @@
         }
     }
 
+    private boolean canReadMangeOwnCalls(String message) {
+        try {
+            mContext.enforceCallingOrSelfPermission(MANAGE_OWN_CALLS, message);
+            return true;
+        } catch (SecurityException e) {
+            return false;
+        }
+    }
+
     private boolean canReadPhoneNumbers(String callingPackage, String callingFeatureId,
             String message) {
         boolean targetSdkPreR = false;
@@ -2553,6 +2665,29 @@
         return false;
     }
 
+    /**
+     * Similar to {@link #isCallerSimCallManager}, but works for all SIMs and does not require
+     * {@code accountHandle} to be registered yet.
+     */
+    private boolean isCallerSimCallManagerForAnySim(PhoneAccountHandle accountHandle) {
+        if (isCallerSimCallManager(accountHandle)) {
+            // The caller has already registered a CONNECTION_MANAGER PhoneAccount, so let them pass
+            // (this allows the SIM call manager through in case of SIM switches, where carrier
+            // config may be in a transient state)
+            return true;
+        }
+        // If the caller isn't already registered, then we have to look at the active PSTN
+        // PhoneAccounts and check their carrier configs to see if any point to this one's component
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return !mPhoneAccountRegistrar
+                    .getSimPhoneAccountsFromSimCallManager(accountHandle)
+                    .isEmpty();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     private boolean isPrivilegedDialerCalling(String callingPackage) {
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
 
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 67e3be4..237f039 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -220,7 +220,7 @@
         Log.startSession("TS.init");
         // Wrap this in a try block to ensure session cleanup occurs in the case of error.
         try {
-            mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache,
+            mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, mLock, defaultDialerCache,
                     packageName -> AppLabelProxy.Util.getAppLabel(
                             mContext.getPackageManager(), packageName));
 
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 337d383..bd2c6f1 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -650,6 +650,14 @@
                 mHfpActiveDeviceCache != null;
     }
 
+    public boolean isCachedLeAudioDevice(BluetoothDevice device) {
+        return mLeAudioActiveDeviceCache != null && mLeAudioActiveDeviceCache.equals(device);
+    }
+
+    public boolean isCachedHearingAidDevice(BluetoothDevice device) {
+        return mHearingAidActiveDeviceCache != null && mHearingAidActiveDeviceCache.equals(device);
+    }
+
     public Collection<BluetoothDevice> getConnectedDevices() {
         return mDeviceManager.getUniqueConnectedDevices();
     }
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 0c30a16..51b1bc6 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -319,7 +319,6 @@
                 mCallsManager.getCurrentUserHandle(), mPackageName, connection)) {
             Log.i(this, "Call screening service binding failed.");
             resultFuture.complete(mPriorStageResult);
-            mConnection = null;
         } else {
             mConnection = connection;
         }
diff --git a/src/com/android/server/telecom/components/ErrorDialogActivity.java b/src/com/android/server/telecom/components/ErrorDialogActivity.java
index 3618b77..fd55a35 100644
--- a/src/com/android/server/telecom/components/ErrorDialogActivity.java
+++ b/src/com/android/server/telecom/components/ErrorDialogActivity.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom.components;
 
-import com.android.server.telecom.FrameworksUtils;
 import com.android.server.telecom.R;
 
 import android.app.Activity;
@@ -85,7 +84,7 @@
             }
         };
 
-        final AlertDialog errorDialog = FrameworksUtils.makeAlertDialogBuilder(this)
+        final AlertDialog errorDialog = new AlertDialog.Builder(this)
                 .setMessage(msg).setPositiveButton(android.R.string.ok, clickListener)
                         .setOnCancelListener(cancelListener).create();
 
@@ -98,7 +97,7 @@
     }
 
     private void showMissingVoicemailErrorDialog() {
-        FrameworksUtils.makeAlertDialogBuilder(this)
+        final AlertDialog errorDialog = new AlertDialog.Builder(this)
                 .setTitle(R.string.no_vm_number)
                 .setMessage(R.string.no_vm_number_msg)
                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index ca8fef7..1d85884 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -65,7 +65,7 @@
             Intent intent = getIntent();
             verifyCallAction(intent);
             final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
-            final UserHandle userHandle = new UserHandle(userManager.getUserHandle());
+            final UserHandle userHandle = new UserHandle(userManager.getProcessUserId());
             // Once control flow has passed to this activity, it is no longer guaranteed that we can
             // accurately determine whether the calling package has the CALL_PHONE runtime permission.
             // At this point in time we trust that the ActivityManager has already performed this
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index ae76708..cad7b4c 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -80,11 +80,6 @@
      */
     public void processIntent(Intent intent, String callingPackageName,
             boolean canCallNonEmergency, boolean isLocalInvocation) {
-        // Ensure call intents are not processed on devices that are not capable of calling.
-        if (!isVoiceCapable()) {
-            return;
-        }
-
         String action = intent.getAction();
 
         if (Intent.ACTION_CALL.equals(action) ||
@@ -160,16 +155,6 @@
     }
 
     /**
-     * Returns whether the device is voice-capable (e.g. a phone vs a tablet).
-     *
-     * @return {@code True} if the device is voice-capable.
-     */
-    private boolean isVoiceCapable() {
-        return ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE))
-                .isVoiceCapable();
-    }
-
-    /**
      * Potentially trampolines the intent to Telecom via TelecomServiceImpl.
      * If the caller is local to the Telecom service, we send the intent to Telecom without
      * sending it through TelecomServiceImpl.
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index 036fb91..bc54e11 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -54,7 +54,6 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.server.telecom.FrameworksUtils;
 import com.android.server.telecom.R;
 
 
@@ -215,7 +214,7 @@
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         return new CursorLoader(this, BlockedNumberContract.BlockedNumbers.CONTENT_URI,
                 PROJECTION, SELECTION, null,
-                BlockedNumberContract.BlockedNumbers.COLUMN_ID + " DESC");
+                BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + " ASC");
     }
 
     @Override
@@ -246,7 +245,7 @@
         final EditText editText = (EditText) dialogView.findViewById(R.id.add_blocked_number);
         editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
         editText.addTextChangedListener(this);
-        AlertDialog dialog = FrameworksUtils.makeAlertDialogBuilder(this)
+        AlertDialog dialog = new AlertDialog.Builder(this)
                 .setView(dialogView)
                 .setPositiveButton(R.string.block_button, new DialogInterface.OnClickListener() {
                     public void onClick(DialogInterface dialog, int id) {
@@ -261,23 +260,23 @@
                 })
                 .create();
         dialog.setOnShowListener(new AlertDialog.OnShowListener() {
-                    @Override
-                    public void onShow(DialogInterface dialog) {
-                        mBlockButton = ((AlertDialog) dialog)
-                                .getButton(AlertDialog.BUTTON_POSITIVE);
-                        mBlockButtonNegative = ((AlertDialog) dialog)
-                                .getButton(AlertDialog.BUTTON_NEGATIVE);
-                        mBlockButton.setAllCaps(false);
-                        mBlockButtonNegative.setAllCaps(false);
-                        mBlockButton.setEnabled(false);
-                        // show keyboard
-                        InputMethodManager inputMethodManager =
-                                (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
-                        inputMethodManager.showSoftInput(editText,
-                                InputMethodManager.SHOW_IMPLICIT);
+            @Override
+            public void onShow(DialogInterface dialog) {
+                mBlockButton = ((AlertDialog) dialog)
+                        .getButton(AlertDialog.BUTTON_POSITIVE);
+                mBlockButtonNegative = ((AlertDialog) dialog)
+                        .getButton(AlertDialog.BUTTON_NEGATIVE);
+                mBlockButton.setAllCaps(false);
+                mBlockButtonNegative.setAllCaps(false);
+                mBlockButton.setEnabled(false);
+                // show keyboard
+                InputMethodManager inputMethodManager =
+                        (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+                inputMethodManager.showSoftInput(editText,
+                        InputMethodManager.SHOW_IMPLICIT);
 
-                    }
-                });
+            }
+        });
         dialog.show();
     }
 
@@ -337,4 +336,4 @@
         }
         mAddButton.setEnabled(true);
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
index 333e451..df68f6e 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
@@ -29,7 +29,6 @@
 import android.widget.SimpleCursorAdapter;
 import android.widget.TextView;
 
-import com.android.server.telecom.FrameworksUtils;
 import com.android.server.telecom.R;
 
 public class BlockedNumbersAdapter extends SimpleCursorAdapter {
@@ -73,7 +72,7 @@
         Spannable messageSpannable = new SpannableString(message);
         PhoneNumberUtils.addTtsSpan(messageSpannable, startingPosition,
                 startingPosition + formattedNumber.length());
-        AlertDialog dialog = FrameworksUtils.makeAlertDialogBuilder(context)
+        AlertDialog dialog = new AlertDialog.Builder(context)
                 .setMessage(messageSpannable)
                 .setPositiveButton(R.string.unblock_button,
                         new DialogInterface.OnClickListener() {
diff --git a/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java b/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
index 232546b..5f42b37 100644
--- a/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
+++ b/src/com/android/server/telecom/settings/CallBlockDisabledActivity.java
@@ -22,7 +22,6 @@
 import android.os.Bundle;
 import android.provider.BlockedNumberContract;
 
-import com.android.server.telecom.FrameworksUtils;
 import com.android.server.telecom.R;
 
 /**
@@ -51,7 +50,7 @@
             return;
         }
 
-        AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(this);
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
         mDialog = builder
                 .setTitle(R.string.phone_strings_emergency_call_made_dialog_title_txt)
                 .setMessage(R.string
diff --git a/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java b/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java
index b5c850e..5aa80c6 100644
--- a/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java
+++ b/src/com/android/server/telecom/ui/CallRedirectionTimeoutDialogActivity.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom.ui;
 
-import com.android.server.telecom.FrameworksUtils;
 import com.android.server.telecom.R;
 
 import android.app.Activity;
@@ -46,7 +45,7 @@
         Log.i(this, "showDialog: timeout redirection with %s", redirectionAppName);
         CharSequence message = getString(
                 R.string.alert_redirect_outgoing_call_timeout, redirectionAppName);
-        final AlertDialog errorDialog = FrameworksUtils.makeAlertDialogBuilder(this)
+        final AlertDialog errorDialog = new AlertDialog.Builder(this)
                 .setMessage(message)
                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                     @Override
diff --git a/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java b/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
index e9f99b6..4735e3c 100644
--- a/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
+++ b/src/com/android/server/telecom/ui/ConfirmCallDialogActivity.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom.ui;
 
-import com.android.server.telecom.FrameworksUtils;
 import com.android.server.telecom.R;
 import com.android.server.telecom.TelecomBroadcastIntentProcessor;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
@@ -49,7 +48,7 @@
     private void showDialog(final String callId, CharSequence ongoingAppName) {
         Log.i(this, "showDialog: confirming callId=%s, ongoing=%s", callId, ongoingAppName);
         CharSequence message = getString(R.string.alert_outgoing_call, ongoingAppName);
-        final AlertDialog errorDialog = FrameworksUtils.makeAlertDialogBuilder(this)
+        final AlertDialog errorDialog = new AlertDialog.Builder(this)
                 .setMessage(message)
                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                     @Override
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 48295c4..6b97f97 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -17,15 +17,45 @@
 package com.android.server.telecom.ui;
 
 import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.app.admin.DevicePolicyResources.Strings.Telecomm.NOTIFICATION_MISSED_WORK_CALL_TITLE;
 
 import android.annotation.NonNull;
 import android.app.BroadcastOptions;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.app.admin.DevicePolicyManager;
+import android.content.AsyncQueryHandler;
 import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
 import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.CallLog.Calls;
+import android.telecom.CallerInfo;
+import android.telecom.Log;
 import android.telecom.Logging.Runnable;
+import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.BidiFormatter;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManagerListenerBase;
@@ -40,38 +70,6 @@
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
 
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.TaskStackBuilder;
-import android.content.AsyncQueryHandler;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
-import android.os.UserHandle;
-import android.provider.CallLog.Calls;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.text.BidiFormatter;
-import android.text.TextDirectionHeuristics;
-import android.text.TextUtils;
-
-import android.telecom.CallerInfo;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import java.lang.Override;
-import java.lang.String;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -270,7 +268,9 @@
                     createClearMissedCallsPendingIntent(userHandle))
             .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount)
             .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
-                    callInfo == null ? null : callInfo.getPhoneNumber());
+                    callInfo == null ? null : callInfo.getPhoneNumber())
+            .putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                    callInfo == null ? null : callInfo.getPhoneAccountHandle());
 
         if (missedCallCount == 1 && callInfo != null) {
             final Uri handleUri = callInfo.getHandle();
@@ -328,7 +328,7 @@
             return;
         }
 
-        final int titleResId;
+        final String titleText;
         final String expandedText;  // The text in the notification's line 1 and 2.
 
         // Display the first line of the notification:
@@ -339,12 +339,14 @@
 
             CallerInfo ci = callInfo.getCallerInfo();
             if (ci != null && ci.userType == CallerInfo.USER_TYPE_WORK) {
-                titleResId = R.string.notification_missedWorkCallTitle;
+                titleText = mContext.getSystemService(DevicePolicyManager.class).getResources()
+                        .getString(NOTIFICATION_MISSED_WORK_CALL_TITLE, () ->
+                                mContext.getString(R.string.notification_missedWorkCallTitle));
             } else {
-                titleResId = R.string.notification_missedCallTitle;
+                titleText = mContext.getString(R.string.notification_missedCallTitle);
             }
         } else {
-            titleResId = R.string.notification_missedCallsTitle;
+            titleText = mContext.getString(R.string.notification_missedCallsTitle);
             expandedText =
                     mContext.getString(R.string.notification_missedCallsMsg, missedCallCounts);
         }
@@ -362,7 +364,7 @@
                 .setContentTitle(mContext.getText(R.string.userCallActivityLabel))
                 // Notification details shows that there are missed call(s), but does not reveal
                 // the missed caller information.
-                .setContentText(mContext.getText(titleResId))
+                .setContentText(titleText)
                 .setContentIntent(createCallLogPendingIntent(userHandle))
                 .setAutoCancel(true)
                 .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle));
@@ -373,7 +375,7 @@
                 .setColor(mContext.getResources().getColor(R.color.theme_color))
                 .setWhen(callInfo.getCreationTimeMillis())
                 .setShowWhen(true)
-                .setContentTitle(mContext.getText(titleResId))
+                .setContentTitle(titleText)
                 .setContentText(expandedText)
                 .setContentIntent(createCallLogPendingIntent(userHandle))
                 .setAutoCancel(true)
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index 322c94c..32bc372 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -124,7 +124,7 @@
     }
 
     public Call getCall(int position) {
-        return mCalls.get(position);
+        return (position < mCalls.size()) ? mCalls.get(position) : null;
     }
 
     public void addCall(Call call) {
diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
index 010d6ee..f17af2c 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
@@ -4,6 +4,7 @@
 
 import android.app.Activity;
 import android.app.UiModeManager;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -133,9 +134,11 @@
     }
 
     private void setDefault() {
-        final Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER);
-        intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName());
-        startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER);
+        RoleManager roleManager = getSystemService(RoleManager.class);
+        if(roleManager!= null) {
+            startActivityForResult(roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER),
+                    REQUEST_CODE_SET_DEFAULT_DIALER);
+        }
     }
 
     private void placeCall() {
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index 2a304a4..5da7f31 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -18,6 +18,7 @@
 
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
 import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -209,7 +210,8 @@
         assertEquals(0, callAnalytics2.endTime);
         long missedReason1 = callAnalytics1.missedReason;
         assertTrue(missedReason1 == MISSED_REASON_NOT_MISSED
-                || missedReason1 == USER_MISSED_CALL_FILTERS_TIMEOUT);
+                || ((missedReason1 & USER_MISSED_CALL_FILTERS_TIMEOUT) > 0)
+                || ((missedReason1 & USER_MISSED_NO_VIBRATE) > 0));
         assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics2.missedReason);
 
         assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 058eb12..049a501 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -37,6 +37,7 @@
 import android.content.Context;
 import android.content.IContentProvider;
 import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Binder;
@@ -627,22 +628,27 @@
 
         mInCallServiceFixtureX.mInCallAdapter.mute(true);
         verify(mAudioService, timeout(TEST_TIMEOUT))
-                .setMicrophoneMute(eq(true), any(String.class), any(Integer.class));
+                .setMicrophoneMute(eq(true), any(String.class), any(Integer.class),
+                        nullable(String.class));
         mInCallServiceFixtureX.mInCallAdapter.mute(false);
         verify(mAudioService, timeout(TEST_TIMEOUT))
-                .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
+                .setMicrophoneMute(eq(false), any(String.class), any(Integer.class),
+                        nullable(String.class));
 
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
                 .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
-        verify(audioManager, timeout(TEST_TIMEOUT))
-                .setSpeakerphoneOn(true);
+        ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor =
+                ArgumentCaptor.forClass(AudioDeviceInfo.class);
+        verify(audioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
+                infoArgumentCaptor.capture());
+        assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType());
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
                 .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
         // setSpeakerPhoneOn(false) gets called once during the call initiation phase
         verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
-                .setSpeakerphoneOn(false);
+                .clearCommunicationDevice();
 
         mConnectionServiceFixtureA.
                 sendSetDisconnected(outgoing.mConnectionId, DisconnectCause.REMOTE);
@@ -1042,7 +1048,9 @@
     @Test
     public void testIsOutgoingCallPermitted() throws Exception {
         assertTrue(mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle(),
+                        mPhoneAccountSelfManaged.getAccountHandle().getComponentName()
+                                .getPackageName()));
     }
 
     /**
@@ -1059,7 +1067,9 @@
         assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
 
         assertTrue(mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle(),
+                        mPhoneAccountSelfManaged.getAccountHandle().getComponentName()
+                                .getPackageName()));
     }
 
     /**
@@ -1077,7 +1087,9 @@
         assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
 
         assertTrue(mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle(),
+                        mPhoneAccountSelfManaged.getAccountHandle().getComponentName()
+                                .getPackageName()));
     }
 
     /**
@@ -1194,7 +1206,7 @@
 
         ArgumentCaptor<Boolean> muteValueCaptor = ArgumentCaptor.forClass(Boolean.class);
         verify(mAudioService, times(2)).setMicrophoneMute(muteValueCaptor.capture(),
-                any(String.class), any(Integer.class));
+                any(String.class), any(Integer.class), nullable(String.class));
         List<Boolean> muteValues = muteValueCaptor.getAllValues();
         // Check mute status was changed twice with true and false.
         assertTrue(muteValues.get(0));
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 26aca69..81f476e 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -428,6 +428,37 @@
 
     @SmallTest
     @Test
+    public void testConnectDisconnectAudioLeAudio() {
+        receiverUnderTest.setIsInCall(true);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
+        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+        when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(mockAudioDeviceInfo);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                        .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(mockAudioDeviceInfo))
+                       .thenReturn(true);
+
+        mBluetoothDeviceManager.connectAudio(device5.getAddress());
+        verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+        verify(mBluetoothHeadset, never()).connectAudio();
+        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+
+        mBluetoothDeviceManager.disconnectAudio();
+        verify(mockAudioManager).clearCommunicationDevice();
+    }
+
+    @SmallTest
+    @Test
     public void testConnectEarbudLeAudio() {
         receiverUnderTest.setIsInCall(true);
         receiverUnderTest.onReceive(mContext,
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index d923c90..b729f35 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -227,13 +227,16 @@
 
         @Override
         public String toString() {
+            String expectedListenerUpdatesStr = expectedListenerUpdates == null ? ""
+                    : Arrays.stream(expectedListenerUpdates).map(ListenerUpdate::name)
+                            .collect(Collectors.joining(","));
             return "BluetoothRouteTestParameters{" +
                     "name='" + name + '\'' +
                     ", initialBluetoothState='" + initialBluetoothState + '\'' +
                     ", initialDevice=" + initialDevice +
                     ", messageType=" + messageType +
                     ", messageDevice='" + messageDevice + '\'' +
-                    ", expectedListenerUpdate=" + expectedListenerUpdates +
+                    ", expectedListenerUpdate='" + expectedListenerUpdatesStr + '\'' +
                     ", expectedBluetoothInteraction=" + expectedBluetoothInteraction +
                     ", expectedConnectionDevice='" + expectedConnectionDevice + '\'' +
                     ", expectedFinalStateName='" + expectedFinalStateName + '\'' +
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 7af29aa..4adafc8 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -45,6 +45,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.Arrays;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -294,6 +295,60 @@
         verifyProperCleanup();
     }
 
+    @MediumTest
+    @Test
+    public void testRingbackStartStop() {
+        Call call = mock(Call.class);
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+        when(call.getState()).thenReturn(CallState.CONNECTING);
+        when(call.isRingbackRequested()).thenReturn(true);
+
+        mCallAudioManager.onCallAdded(call);
+        assertEquals(call, mCallAudioManager.getForegroundCall());
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+        CallAudioModeStateMachine.MessageArgs expectedArgs =
+                new Builder()
+                        .setHasActiveOrDialingCalls(true)
+                        .setHasRingingCalls(false)
+                        .setHasHoldingCalls(false)
+                        .setIsTonePlaying(false)
+                        .setHasAudioProcessingCalls(false)
+                        .setForegroundCallIsVoip(false)
+                        .setSession(null)
+                        .build();
+        assertMessageArgEquality(expectedArgs, captor.getValue());
+
+        when(call.getState()).thenReturn(CallState.DIALING);
+        mCallAudioManager.onCallStateChanged(call, CallState.CONNECTING, CallState.DIALING);
+        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+        assertMessageArgEquality(expectedArgs, captor.getValue());
+        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+
+        // Ensure we started ringback.
+        verify(mRingbackPlayer).startRingbackForCall(any(Call.class));
+
+        // Report state change from dialing to dialing, which happens when a call is locally
+        // disconnected.
+        mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.DIALING);
+        // Should not have stopped ringback.
+        verify(mRingbackPlayer, never()).stopRingbackForCall(any(Call.class));
+        // Should still only have initial ringback start
+        verify(mRingbackPlayer, times(1)).startRingbackForCall(any(Call.class));
+
+        // Report state to disconnected
+        when(call.getState()).thenReturn(CallState.DISCONNECTED);
+        mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.DISCONNECTED);
+        // Now we should have stopped ringback.
+        verify(mRingbackPlayer).stopRingbackForCall(any(Call.class));
+        // Should still only have initial ringback start recorded from before (don't restart it).
+        verify(mRingbackPlayer, times(1)).startRingbackForCall(any(Call.class));
+    }
+
     @SmallTest
     @Test
     public void testNewCallGoesToAudioProcessing() {
@@ -607,6 +662,21 @@
         verifyProperCleanup();
     }
 
+    @SmallTest
+    @Test
+    public void testGetVoipMode() {
+        Call child = mock(Call.class);
+        when(child.getIsVoipAudioMode()).thenReturn(true);
+
+        Call conference = mock(Call.class);
+        when(conference.isConference()).thenReturn(true);
+        when(conference.getIsVoipAudioMode()).thenReturn(false);
+        when(conference.getChildCalls()).thenReturn(Arrays.asList(child));
+
+        assertTrue(mCallAudioManager.isCallVoip(conference));
+        assertTrue(mCallAudioManager.isCallVoip(child));
+    }
+
     private Call createSimulatedRingingCall() {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 91ec7f3..6092293 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.os.HandlerThread;
@@ -505,6 +506,37 @@
 
     @SmallTest
     @Test
+    public void testDockWhenInQuiescentState() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper());
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        // Raise a dock connect event.
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_DOCK);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        assertTrue(!stateMachine.isInActiveState());
+        verify(mockAudioManager, never()).setSpeakerphoneOn(eq(true));
+
+        // Raise a dock disconnect event.
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.DISCONNECT_DOCK);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        assertTrue(!stateMachine.isInActiveState());
+        verify(mockAudioManager, never()).setSpeakerphoneOn(eq(false));
+    }
+
+    @SmallTest
+    @Test
     public void testFocusChangeFromQuiescentSpeaker() {
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
@@ -531,7 +563,10 @@
         // Make sure that we've successfully switched to the active speaker route and that we've
         // called setSpeakerOn
         assertTrue(stateMachine.isInActiveState());
-        verify(mockAudioManager).setSpeakerphoneOn(true);
+        ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+                AudioDeviceInfo.class);
+        verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+        assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType());
     }
 
     @SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 879ed0f..3eacc3a 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -23,6 +23,7 @@
 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.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
@@ -30,6 +31,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.os.Handler;
@@ -325,11 +327,18 @@
 
         switch (mParams.speakerInteraction) {
             case NONE:
-                verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
+                verify(mockAudioManager, never()).setCommunicationDevice(
+                        any(AudioDeviceInfo.class));
                 break;
-            case ON: // fall through
+            case ON:
+                ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+                        AudioDeviceInfo.class);
+                verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+                assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+                        infoArgumentCaptor.getValue().getType());
+                break;
             case OFF:
-                verify(mockAudioManager).setSpeakerphoneOn(mParams.speakerInteraction == ON);
+                verify(mockAudioManager).clearCommunicationDevice();
                 break;
             case OPTIONAL:
                 // optional, don't test
@@ -823,6 +832,30 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
         ));
 
+        params.add(new RoutingTestParameters(
+                "Connect dock from earpiece", // name
+                CallAudioState.ROUTE_EARPIECE, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // availableRoutes
+                ON, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.CONNECT_DOCK, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // expectedAvailRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect dock from speaker", // name
+                CallAudioState.ROUTE_SPEAKER, // initialRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // availableRoutes
+                OFF, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_DOCK, // action
+                CallAudioState.ROUTE_EARPIECE, // expectedRoute
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, // expectedAvailRoutes
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED // earpieceControl
+        ));
+
         return params;
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index da72933..6fd8334 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -1625,6 +1625,57 @@
         verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class));
     }
 
+    @Test
+    public void testIsInSelfManagedCallOnlyManaged() {
+        Call managedCall = createCall(SIM_1_HANDLE, CallState.ACTIVE);
+        managedCall.setIsSelfManaged(false);
+        mCallsManager.addCall(managedCall);
+
+        // Certainly nothing from the self managed handle.
+        assertFalse(mCallsManager.isInSelfManagedCall(
+                SELF_MANAGED_HANDLE.getComponentName().getPackageName(),
+                SELF_MANAGED_HANDLE.getUserHandle()));
+        // And nothing in a random other package.
+        assertFalse(mCallsManager.isInSelfManagedCall(
+                "com.foo",
+                SELF_MANAGED_HANDLE.getUserHandle()));
+        // And this method is only checking self managed not managed.
+        assertFalse(mCallsManager.isInSelfManagedCall(
+                SIM_1_HANDLE.getComponentName().getPackageName(),
+                SELF_MANAGED_HANDLE.getUserHandle()));
+    }
+
+    @Test
+    public void testIsInSelfManagedCallOnlySelfManaged() {
+        Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        selfManagedCall.setIsSelfManaged(true);
+        mCallsManager.addCall(selfManagedCall);
+
+        assertTrue(mCallsManager.isInSelfManagedCall(
+                SELF_MANAGED_HANDLE.getComponentName().getPackageName(),
+                SELF_MANAGED_HANDLE.getUserHandle()));
+        assertFalse(mCallsManager.isInSelfManagedCall(
+                "com.foo",
+                SELF_MANAGED_HANDLE.getUserHandle()));
+        assertFalse(mCallsManager.isInSelfManagedCall(
+                SIM_1_HANDLE.getComponentName().getPackageName(),
+                SELF_MANAGED_HANDLE.getUserHandle()));
+
+        Call managedCall = createCall(SIM_1_HANDLE, CallState.ACTIVE);
+        managedCall.setIsSelfManaged(false);
+        mCallsManager.addCall(managedCall);
+
+        // Still not including managed
+        assertFalse(mCallsManager.isInSelfManagedCall(
+                SIM_1_HANDLE.getComponentName().getPackageName(),
+                SELF_MANAGED_HANDLE.getUserHandle()));
+
+        // Also shouldn't be something in another user's version of the same package.
+        assertFalse(mCallsManager.isInSelfManagedCall(
+                SELF_MANAGED_HANDLE.getComponentName().getPackageName(),
+                new UserHandle(90210)));
+    }
+
     private Call addSpyCall() {
         return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
     }
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 94c4321..639ac22 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -51,7 +51,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
-import android.location.Country;
 import android.location.CountryDetector;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
@@ -59,17 +58,15 @@
 import android.os.Handler;
 import android.os.IInterface;
 import android.os.PersistableBundle;
-import android.os.PowerWhitelistManager;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.Vibrator;
 import android.os.VibratorManager;
 import android.permission.PermissionCheckerManager;
-import android.telecom.CallAudioState;
 import android.telecom.ConnectionService;
 import android.telecom.Log;
 import android.telecom.InCallService;
-import android.telecom.PhoneAccount;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
@@ -224,6 +221,8 @@
                     return mTelephonyRegistryManager;
                 case Context.UI_MODE_SERVICE:
                     return mUiModeManager;
+                case Context.VIBRATOR_SERVICE:
+                    return mVibrator;
                 case Context.VIBRATOR_MANAGER_SERVICE:
                     return mVibratorManager;
                 case Context.PERMISSION_CHECKER_SERVICE:
@@ -253,6 +252,8 @@
                 return Context.TELEPHONY_REGISTRY_SERVICE;
             } else if (svcClass == UiModeManager.class) {
                 return Context.UI_MODE_SERVICE;
+            } else if (svcClass == Vibrator.class) {
+                return Context.VIBRATOR_SERVICE;
             } else if (svcClass == VibratorManager.class) {
                 return Context.VIBRATOR_MANAGER_SERVICE;
             } else if (svcClass == PermissionCheckerManager.class) {
@@ -436,6 +437,7 @@
         private int mAudioStreamValue = 1;
         private int mMode = AudioManager.MODE_NORMAL;
         private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
+        private AudioDeviceInfo mCommunicationDevice;
 
         public FakeAudioManager(Context context) {
             super(context);
@@ -492,7 +494,18 @@
         }
 
         @Override
+        public void clearCommunicationDevice() {
+            mCommunicationDevice = null;
+        }
+
+        @Override
+        public AudioDeviceInfo getCommunicationDevice() {
+            return mCommunicationDevice;
+        }
+
+        @Override
         public boolean setCommunicationDevice(AudioDeviceInfo device) {
+            mCommunicationDevice = device;
             return true;
         }
     }
@@ -540,7 +553,7 @@
     private final NotificationManager mNotificationManager = mock(NotificationManager.class);
     private final UserManager mUserManager = mock(UserManager.class);
     private final StatusBarManager mStatusBarManager = mock(StatusBarManager.class);
-    private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
+    private SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
     private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class);
     private final CountryDetector mCountryDetector = mock(CountryDetector.class);
     private final Map<String, IContentProvider> mIContentProviderByUri = new HashMap<>();
@@ -549,6 +562,7 @@
     private final RoleManager mRoleManager = mock(RoleManager.class);
     private final TelephonyRegistryManager mTelephonyRegistryManager =
             mock(TelephonyRegistryManager.class);
+    private final Vibrator mVibrator = mock(Vibrator.class);
     private final VibratorManager mVibratorManager = mock(VibratorManager.class);
     private final UiModeManager mUiModeManager = mock(UiModeManager.class);
     private final PermissionCheckerManager mPermissionCheckerManager =
@@ -620,7 +634,10 @@
         }
 
         when(mPermissionInfo.isAppOp()).thenReturn(true);
+        when(mVibrator.getDefaultVibrationIntensity(anyInt()))
+                .thenReturn(Vibrator.VIBRATION_INTENSITY_MEDIUM);
         when(mVibratorManager.getVibratorIds()).thenReturn(new int[0]);
+        when(mVibratorManager.getDefaultVibrator()).thenReturn(mVibrator);
 
         // Used in CreateConnectionProcessor to rank emergency numbers by viability.
         // For the test, make them all equal to INVALID so that the preferred PhoneAccount will be
@@ -652,6 +669,7 @@
         // Make sure we do not hide PII during testing.
         Log.setTag("TelecomTEST");
         Log.setIsExtendedLoggingEnabled(true);
+        Log.setUnitTestingEnabled(true);
         Log.VERBOSE = true;
     }
 
@@ -735,10 +753,18 @@
         mTelecomManager = telecomManager;
     }
 
+    public void setSubscriptionManager(SubscriptionManager subscriptionManager) {
+        mSubscriptionManager = subscriptionManager;
+    }
+
     public TelephonyManager getTelephonyManager() {
         return mTelephonyManager;
     }
 
+    public CarrierConfigManager getCarrierConfigManager() {
+        return mCarrierConfigManager;
+    }
+
     public NotificationManager getNotificationManager() {
         return mNotificationManager;
     }
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index 1a649dc..cb376af 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -21,6 +21,7 @@
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.UserHandle;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -50,7 +51,10 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Random;
+import java.util.UUID;
 
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
@@ -611,6 +615,71 @@
         verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
     }
 
+    /**
+     * Tests to verify that the
+     * {@link CreateConnectionProcessor#sortSimPhoneAccountsForEmergency(List, PhoneAccount)} can
+     * successfully sort without running into sort issues related to the hashcodes of the
+     * PhoneAccounts.
+     */
+    @Test
+    public void testSortIntegrity() {
+        // Note: 5L was chosen as a random seed on purpose since in combination with a count of
+        // 500 accounts it would result in a crash in the sort algorithm.
+        ArrayList<PhoneAccount> accounts = generateRandomPhoneAccounts(5L, 500);
+        try {
+            mTestCreateConnectionProcessor.sortSimPhoneAccountsForEmergency(accounts,
+                    null);
+        } catch (Exception e) {
+            fail("Failed to sort phone accounts");
+        }
+    }
+
+    /**
+     * Generates random phone accounts.
+     * @param seed random seed to use for random UUIDs; passed in for determinism.
+     * @param count How many phone accounts to use.
+     * @return Random phone accounts.
+     */
+    private ArrayList<PhoneAccount> generateRandomPhoneAccounts(long seed, int count) {
+        Random random = new Random(seed);
+        ArrayList<PhoneAccount> accounts = new ArrayList<>();
+        for (int ix = 0 ; ix < count; ix++) {
+            ArrayList<String> supportedSchemes = new ArrayList<>();
+            supportedSchemes.add("tel");
+            supportedSchemes.add("sip");
+            supportedSchemes.add("custom");
+
+            PhoneAccountHandle handle = new PhoneAccountHandle(
+                    ComponentName.unflattenFromString(
+                            "com.android.server.telecom.testapps/"
+                                    + "com.android.server.telecom.testapps"
+                                    + ".SelfManagedConnectionService"),
+                    getRandomUuid(random).toString(), new UserHandle(0));
+            PhoneAccount acct = new PhoneAccount.Builder(handle, "TelecommTests")
+                    .setAddress(Uri.fromParts("tel", "555-1212", null))
+                    .setCapabilities(3080)
+                    .setHighlightColor(0)
+                    .setShortDescription("test_" + ix)
+                    .setSupportedUriSchemes(supportedSchemes)
+                    .setIsEnabled(true)
+                    .setSupportedAudioRoutes(15)
+                    .build();
+            accounts.add(acct);
+        }
+        return accounts;
+    }
+
+    /**
+     * Returns a random UUID based on the passed in Random generator.
+     * @param random Random generator.
+     * @return The UUID.
+     */
+    private UUID getRandomUuid(Random random) {
+        byte[] array = new byte[16];
+        random.nextBytes(array);
+        return UUID.nameUUIDFromBytes(array);
+    }
+
     private PhoneAccount makeEmergencyTestPhoneAccount(String id, int capabilities) {
         final PhoneAccount emergencyPhoneAccount = makeQuickAccount(id, capabilities |
                 PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
diff --git a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
new file mode 100644
index 0000000..6d15e60
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 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 com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.HeadsetMediaButton;
+import com.android.server.telecom.TelecomSystem;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class HeadsetMediaButtonTest extends TelecomTestCase {
+    private static final int TEST_TIMEOUT_MILLIS = 1000;
+
+    private HeadsetMediaButton mHeadsetMediaButton;
+
+    @Mock private CallsManager mMockCallsManager;
+    @Mock private HeadsetMediaButton.MediaSessionAdapter mMediaSessionAdapter;
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mHeadsetMediaButton = new HeadsetMediaButton(mContext, mMockCallsManager, mLock,
+                mMediaSessionAdapter);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        mHeadsetMediaButton = null;
+        super.tearDown();
+    }
+
+    /**
+     * Nominal case; just add a call and remove it.
+     */
+    @Test
+    public void testAddCall() {
+        Call regularCall = getRegularCall();
+
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(true));
+        // ... and thus we see how the original code isn't amenable to tests.
+        when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+        mHeadsetMediaButton.onCallRemoved(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(false));
+    }
+
+    /**
+     * Test a case where a regular call becomes an external call, and back again.
+     */
+    @Test
+    public void testRegularCallThatBecomesExternal() {
+        Call regularCall = getRegularCall();
+
+        // Start with a regular old call.
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(true));
+        when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+        // Change so it is external.
+        when(regularCall.isExternalCall()).thenReturn(true);
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+        mHeadsetMediaButton.onExternalCallChanged(regularCall, true);
+        // Expect to set session inactive.
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(false));
+
+        // For good measure lets make it non-external again.
+        when(regularCall.isExternalCall()).thenReturn(false);
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+        mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
+        // Expect to set session active.
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(true));
+    }
+
+    /**
+     * @return a mock call instance of a regular non-external call.
+     */
+    private Call getRegularCall() {
+        Call regularCall = Mockito.mock(Call.class);
+        when(regularCall.isExternalCall()).thenReturn(false);
+        return regularCall;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index f661878..ddacf43 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -64,6 +64,8 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -102,7 +104,9 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
@@ -120,6 +124,8 @@
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
+import libcore.junit.util.compat.CoreCompatChangeRule;
+
 @RunWith(JUnit4.class)
 public class InCallControllerTests extends TelecomTestCase {
     @Mock CallsManager mMockCallsManager;
@@ -139,6 +145,9 @@
     @Mock NotificationManager mNotificationManager;
     @Mock PermissionInfo mMockPermissionInfo;
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     private static final int CURRENT_USER_ID = 900973;
     private static final String DEF_PKG = "defpkg";
     private static final String DEF_CLASS = "defcls";
@@ -909,7 +918,7 @@
 
    /**
      * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
-     * supports third party app
+     * supports third party app.
      */
     @MediumTest
     @Test
@@ -924,6 +933,11 @@
                     true /* system */, false /* external calls */, false /* self mgd in default */,
                     false /* self mgd in car*/);
 
+            ApplicationInfo applicationInfo = new ApplicationInfo();
+            applicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
+            // set up mock call for ICSC#sendCrashedInCallServiceNotification(String)
+            when(mMockContext.getApplicationInfo()).thenReturn(applicationInfo);
+
             // Enable Third Party Companion App
             ExtendedMockito.doReturn(PermissionChecker.PERMISSION_GRANTED).when(() ->
                     PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
@@ -950,6 +964,7 @@
 
             // Should have next bound to the third party app op non ui app.
             verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS);
+
         } finally {
             mockitoSession.finishMocking();
         }
@@ -967,6 +982,13 @@
                 true /* system */, false /* external calls */, false /* self mgd in default */,
                 false /* self mgd in car*/, true /* self managed in nonui */);
 
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.targetSdkVersion = Build.VERSION_CODES.TIRAMISU;
+        when(mMockContext.getApplicationInfo()).thenReturn(applicationInfo);
+        // Package doesn't have metadata of TelecomManager.METADATA_IN_CALL_SERVICE_UI should
+        // not be the default dialer. This is to mock the default dialer is null in this case.
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(null);
+
         // we should bind to only the non ui app.
         mInCallController.bindToServices(mMockCall);
 
@@ -984,6 +1006,10 @@
 
         // Should have bound to the third party non ui app.
         verifyBinding(bindIntentCaptor, 0, NONUI_PKG, NONUI_CLASS);
+
+        // Verify notification is not sent by NotificationManager
+        verify(mNotificationManager, times(0)).notify(eq(InCallController.NOTIFICATION_TAG),
+                eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any());
     }
 
     @MediumTest
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index cb0497a..eadda0d 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -19,23 +19,32 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.media.AudioManager;
 import android.media.MediaPlayer;
 import android.media.ToneGenerator;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import androidx.test.filters.FlakyTest;
-
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.DockManager;
 import com.android.server.telecom.InCallTonePlayer;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -47,19 +56,21 @@
 @RunWith(JUnit4.class)
 public class InCallTonePlayerTest extends TelecomTestCase {
 
+    private static final long TEST_TIMEOUT = 5000L;
     private InCallTonePlayer.Factory mFactory;
-
-    @Mock
     private CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
 
-    @Mock
-    private TelecomSystem.SyncRoot mLock;
-
-    @Mock
-    private ToneGenerator mToneGenerator;
-
-    @Mock
-    private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
+    @Mock private BluetoothRouteManager mBluetoothRouteManager;
+    @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    @Mock private Timeouts.Adapter mTimeoutsAdapter;
+    @Mock private BluetoothDeviceManager mBluetoothDeviceManager;
+    @Mock private TelecomSystem.SyncRoot mLock;
+    @Mock private ToneGenerator mToneGenerator;
+    @Mock private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
+    @Mock private WiredHeadsetManager mWiredHeadsetManager;
+    @Mock private DockManager mDockManager;
+    @Mock private BluetoothDevice mDevice;
+    @Mock private BluetoothAdapter mBluetoothAdapter;
 
     private InCallTonePlayer.MediaPlayerAdapter mMediaPlayerAdapter =
             new InCallTonePlayer.MediaPlayerAdapter() {
@@ -87,7 +98,7 @@
 
         @Override
         public int getDuration() {
-            return 0;
+            return 1000;
         }
     };
 
@@ -109,7 +120,11 @@
 
         when(mToneGeneratorFactory.get(anyInt(), anyInt())).thenReturn(mToneGenerator);
         when(mMediaPlayerFactory.get(anyInt(), any())).thenReturn(mMediaPlayerAdapter);
+        doNothing().when(mCallAudioManager).setIsTonePlaying(anyBoolean());
 
+        mCallAudioRoutePeripheralAdapter = new CallAudioRoutePeripheralAdapter(
+                mCallAudioRouteStateMachine, mBluetoothRouteManager, mWiredHeadsetManager,
+                mDockManager);
         mFactory = new InCallTonePlayer.Factory(mCallAudioRoutePeripheralAdapter, mLock,
                 mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter);
         mFactory.setCallAudioManager(mCallAudioManager);
@@ -134,7 +149,43 @@
         verify(mMediaPlayerFactory, never()).get(anyInt(), any());
     }
 
-    @FlakyTest
+    @SmallTest
+    @Test
+    public void testInterruptMediaTone() {
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        assertTrue(mInCallTonePlayer.startTone());
+        // Verify we did play a tone.
+        verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
+        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+
+        mInCallTonePlayer.stopTone();
+        // Timeouts due to threads!
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+
+        // Correctness check: ensure we can't start the tone again.
+        assertFalse(mInCallTonePlayer.startTone());
+    }
+
+    @SmallTest
+    @Test
+    public void testInterruptToneGenerator() {
+        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        assertTrue(mInCallTonePlayer.startTone());
+        verify(mToneGenerator, timeout(TEST_TIMEOUT)).startTone(anyInt());
+        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+
+        mInCallTonePlayer.stopTone();
+        // Timeouts due to threads!
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+        // Ideally it would be nice to verify this, however release is a native method so appears to
+        // cause flakiness when testing on Cuttlefish.
+        // verify(mToneGenerator, timeout(TEST_TIMEOUT)).release();
+
+        // Correctness check: ensure we can't start the tone again.
+        assertFalse(mInCallTonePlayer.startTone());
+    }
+
     @SmallTest
     @Test
     public void testEndCallToneWhenNotSilenced() {
@@ -142,7 +193,79 @@
         assertTrue(mInCallTonePlayer.startTone());
 
         // Verify we did play a tone.
-        verify(mMediaPlayerFactory, timeout(5000)).get(anyInt(), any());
+        verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(true));
+    }
+
+    @SmallTest
+    @Test
+    public void testRingbackToneAudioStreamHeadset() {
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+        when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
+
+        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+        assertTrue(mInCallTonePlayer.startTone());
+        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+                .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
+        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+    }
+
+    @SmallTest
+    @Test
+    public void testCallWaitingToneAudioStreamHeadset() {
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+        when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
+
+        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+        assertTrue(mInCallTonePlayer.startTone());
+        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+                .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
+        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+    }
+
+    @SmallTest
+    @Test
+    public void testRingbackToneAudioStreamHearingAid() {
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+        when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
+
+        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+        assertTrue(mInCallTonePlayer.startTone());
+        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+                .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
+        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+    }
+
+    @SmallTest
+    @Test
+    public void testCallWaitingToneAudioStreamHearingAid() {
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        mBluetoothDeviceManager.setBluetoothRouteManager(mBluetoothRouteManager);
+        when(mBluetoothRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(mDevice);
+        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+
+        when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
+        when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
+
+        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+        assertTrue(mInCallTonePlayer.startTone());
+        verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
+                .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
         verify(mCallAudioManager).setIsTonePlaying(eq(true));
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
index fd7505b..f935908 100644
--- a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.tests;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.never;
@@ -128,4 +129,34 @@
 
         verify(mWakeLockAdapter, never()).acquire();
     }
+
+    @SmallTest
+    @Test
+    public void testExternalCallStateChangeDuringRinging() throws Exception {
+        // A call is ringing on the local device directly.
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mCall);
+        when(mCall.isExternalCall()).thenReturn(false);
+
+        mInCallWakeLockController.onCallAdded(mCall);
+
+        verify(mWakeLockAdapter).acquire();
+
+        // The call then becomes an external call during ringing. The wake lock should be
+        // released.
+        reset(mWakeLockAdapter);
+        when(mWakeLockAdapter.isHeld()).thenReturn(true);
+        when(mCall.isExternalCall()).thenReturn(true);
+
+        mInCallWakeLockController.onExternalCallChanged(mCall, /* isExternalCall= */ true);
+
+        verify(mWakeLockAdapter).release(0);
+
+        // The call then is pulled to the device, wake up the device and acquire the wake lock.
+        reset(mWakeLockAdapter);
+        when(mCall.isExternalCall()).thenReturn(false);
+
+        mInCallWakeLockController.onExternalCallChanged(mCall, /* isExternalCall= */ true);
+
+        verify(mWakeLockAdapter).acquire();
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/LogUtilsTest.java b/tests/src/com/android/server/telecom/tests/LogUtilsTest.java
new file mode 100644
index 0000000..637dfbc
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/LogUtilsTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 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.junit.Assert.assertTrue;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.LogUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LogUtilsTest extends TelecomTestCase {
+
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Tests LogUtils#initLogging(Context) listeners cannot be initialized more than once by calling
+     * the init function multiple times.  If the listeners are ever re-initialized, log spewing
+     * will occur.
+     *
+     * Note, LogUtils will already be initialized at the start of the testing framework,
+     * so you cannot assume it is 0 at the start of this testing class.
+     */
+    @SmallTest
+    @Test
+    public void testLogUtilsIsNotReInitialized() {
+
+        // assert the listeners of LogUtils are never re-initialized
+        assertTrue(LogUtils.getInitializedCounter() <= 1);
+        // call initLogging an arbitrary amount of times...
+        LogUtils.initLogging(mContext);
+        LogUtils.initLogging(mContext);
+        LogUtils.initLogging(mContext);
+        // assert the listeners of LogUtils are never re-initialized
+        assertTrue(LogUtils.getInitializedCounter() <= 1);
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
index e6cd986..f2f0cd8 100644
--- a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -22,6 +22,11 @@
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
 import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
 import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
+import static android.provider.CallLog.Calls.USER_MISSED_DND_MODE;
+import static android.provider.CallLog.Calls.USER_MISSED_LOW_RING_VOLUME;
+import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
+import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -29,17 +34,21 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationManager;
 import android.content.ContentResolver;
 import android.content.ContentValues;
+import android.content.Context;
 import android.content.IContentProvider;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -47,9 +56,11 @@
 import android.provider.CallLog;
 import android.telecom.DisconnectCause;
 import android.telecom.TelecomManager;
+import android.util.Log;
 
 import com.android.server.telecom.Analytics;
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallIntentProcessor;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
@@ -63,9 +74,13 @@
 import org.mockito.Mock;
 
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 public class MissedInformationTest extends TelecomSystemTest {
-    private static final int TEST_TIMEOUT_MILLIS = 1000;
+    private static final int TEST_TIMEOUT_MILLIS = 2000;
+    private static final long SHORT_RING_TIME = 2000;
+    private static final long LONG_RING_TIME = 6000;
     private static final String TEST_NUMBER = "650-555-1212";
     private static final String TEST_NUMBER_1 = "7";
     private static final String PACKAGE_NAME = "com.android.server.telecom.tests";
@@ -77,9 +92,13 @@
     @Mock Call mEmergencyCall;
     @Mock Analytics.CallInfo mCallInfo;
     @Mock Call mIncomingCall;
+    @Mock AudioManager mAudioManager;
+    @Mock NotificationManager mNotificationManager;
+
     private CallsManager mCallsManager;
     private CallIntentProcessor.AdapterImpl mAdapter;
     private PackageManager mPackageManager;
+    private CountDownLatch mCountDownLatch;
 
     @Override
     @Before
@@ -87,6 +106,8 @@
         super.setUp();
         mCallsManager = mTelecomSystem.getCallsManager();
         mAdapter = new CallIntentProcessor.AdapterImpl(mCallsManager.getDefaultDialerCache());
+        mNotificationManager = spy((NotificationManager) mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE));
         when(mContentResolver.getPackageName()).thenReturn(PACKAGE_NAME);
         when(mContentResolver.acquireProvider(any(String.class))).thenReturn(mContentProvider);
         when(mContentProvider.call(any(String.class), any(String.class),
@@ -95,6 +116,7 @@
         doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
         mPackageManager = mContext.getPackageManager();
         when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
+        mCountDownLatch  = new CountDownLatch(1);
     }
 
     @Override
@@ -202,6 +224,7 @@
                 .setShouldAllowCall(true)
                 .build();
         mCallsManager.onCallFilteringComplete(mIncomingCall, result, true);
+        assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
         mCallsManager.markCallAsDisconnected(mIncomingCall,
                     new DisconnectCause(DisconnectCause.MISSED));
         ContentValues values = verifyInsertionWithCapture();
@@ -222,6 +245,7 @@
                 .setCallScreeningComponentName(CALL_SCREENING_COMPONENT_NAME)
                 .build();
         mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+        assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
         assertTrue(mIncomingCall.isIncoming());
         mCallsManager.markCallAsDisconnected(mIncomingCall,
                 new DisconnectCause(DisconnectCause.MISSED));
@@ -237,6 +261,132 @@
         assertTrue((missedReason & USER_MISSED_CALL_SCREENING_SERVICE_SILENCED) > 0);
     }
 
+    @Test
+    public void testShortRing() throws Exception {
+        setUpIncomingCall();
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .build();
+        mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+        assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+        when(mClockProxy.elapsedRealtime()).thenReturn(1L + SHORT_RING_TIME);
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertTrue((missedReason & USER_MISSED_SHORT_RING) > 0);
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertTrue((missedReason & USER_MISSED_SHORT_RING) > 0);
+    }
+
+    @Test
+    public void testLongRing() throws Exception {
+        setUpIncomingCall();
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .build();
+        mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+        assertTrue(mCountDownLatch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+        when(mClockProxy.elapsedRealtime()).thenReturn(1L + LONG_RING_TIME);
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertEquals(0, missedReason & USER_MISSED_SHORT_RING);
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertEquals(0, missedReason & USER_MISSED_SHORT_RING);
+    }
+
+    @Test
+    public void testLowRingVolume() throws Exception {
+        CallAudioManager callAudioManager = mCallsManager.getCallAudioManager();
+        when(mSpyContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+        when(mAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+        setUpIncomingCall();
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .build();
+        mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+
+        // Wait for ringer attributes build completed
+        verify(mAudioManager, timeout(TEST_TIMEOUT_MILLIS)).getStreamVolume(anyInt());
+        mCallsManager.getRinger().waitForAttributesCompletion();
+
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertTrue((missedReason & USER_MISSED_LOW_RING_VOLUME) > 0);
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertTrue((missedReason & USER_MISSED_LOW_RING_VOLUME) > 0);
+    }
+
+    @Test
+    public void testNoVibrate() throws Exception {
+        when(mSpyContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        setUpIncomingCall();
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .build();
+        mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+
+        // Wait for ringer attributes build completed
+        verify(mAudioManager, timeout(TEST_TIMEOUT_MILLIS)).getStreamVolume(anyInt());
+        mCallsManager.getRinger().waitForAttributesCompletion();
+
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertTrue((missedReason & USER_MISSED_NO_VIBRATE) > 0);
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertTrue((missedReason & USER_MISSED_NO_VIBRATE) > 0);
+    }
+
+    @Test
+    public void testDndMode() throws Exception {
+        setUpIncomingCall();
+        doReturn(mNotificationManager).when(mSpyContext)
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+        doReturn(false).when(mNotificationManager).matchesCallFilter(any(Bundle.class));
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .build();
+        mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+
+        // Wait for ringer attributes build completed
+        verify(mNotificationManager, timeout(TEST_TIMEOUT_MILLIS))
+                .matchesCallFilter(any(Bundle.class));
+        mCallsManager.getRinger().waitForAttributesCompletion();
+
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertTrue((missedReason & USER_MISSED_DND_MODE) > 0);
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertTrue((missedReason & USER_MISSED_DND_MODE) > 0);
+    }
+
+    @Test
+    public void testNeverRang() throws Exception {
+        setUpIncomingCall();
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertEquals(USER_MISSED_NEVER_RANG, missedReason);
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertEquals(USER_MISSED_NEVER_RANG, missedReason);
+    }
+
     private ContentValues verifyInsertionWithCapture() {
         ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
         verify(mContentResolver, timeout(TEST_TIMEOUT_MILLIS))
@@ -260,8 +410,11 @@
                 null, null, mPhoneAccountA0.getAccountHandle(),
                 Call.CALL_DIRECTION_INCOMING, false, false,
                 mClockProxy, null));
+        doReturn(1L).when(mIncomingCall).getStartRingTime();
+        doAnswer((x) -> {
+            mCountDownLatch.countDown();
+            return 1L;
+        }).when(mClockProxy).elapsedRealtime();
         mIncomingCall.initAnalytics();
-        when(mIncomingCall.getIntentExtras()).thenReturn(new Bundle());
-        when(mIncomingCall.getViaNumber()).thenReturn(TEST_NUMBER);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 6232396..ffa08e2 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -22,10 +22,16 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -36,6 +42,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -43,6 +50,9 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Xml;
@@ -55,6 +65,7 @@
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneAccountRegistrar.DefaultPhoneAccountHandle;
+import com.android.server.telecom.TelecomSystem;
 
 import org.junit.After;
 import org.junit.Before;
@@ -74,6 +85,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -87,7 +99,9 @@
     private final String PACKAGE_1 = "PACKAGE_1";
     private final String PACKAGE_2 = "PACKAGE_2";
     private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     private PhoneAccountRegistrar mRegistrar;
+    @Mock private SubscriptionManager mSubscriptionManager;
     @Mock private TelecomManager mTelecomManager;
     @Mock private DefaultDialerCache mDefaultDialerCache;
     @Mock private AppLabelProxy mAppLabelProxy;
@@ -98,6 +112,7 @@
         super.setUp();
         MockitoAnnotations.initMocks(this);
         mComponentContextFixture.setTelecomManager(mTelecomManager);
+        mComponentContextFixture.setSubscriptionManager(mSubscriptionManager);
         new File(
                 mComponentContextFixture.getTestDouble().getApplicationContext().getFilesDir(),
                 FILE_NAME)
@@ -108,7 +123,7 @@
                 .thenReturn(TEST_LABEL);
         mRegistrar = new PhoneAccountRegistrar(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
-                FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
+                mLock, FILE_NAME, mDefaultDialerCache, mAppLabelProxy);
     }
 
     @Override
@@ -1066,6 +1081,179 @@
         assertEquals(1, deletedAccounts);
     }
 
+    @Test
+    public void testGetSimPhoneAccountsFromSimCallManager() throws Exception {
+        // Register the SIM PhoneAccounts
+        mComponentContextFixture.addConnectionService(
+                makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class));
+        PhoneAccount sim1Account = makeQuickSimAccount(1);
+        PhoneAccountHandle sim1Handle = sim1Account.getAccountHandle();
+        registerAndEnableAccount(sim1Account);
+        PhoneAccount sim2Account = makeQuickSimAccount(2);
+        PhoneAccountHandle sim2Handle = sim2Account.getAccountHandle();
+        registerAndEnableAccount(sim2Account);
+
+        assertEquals(
+            List.of(sim1Handle, sim2Handle), mRegistrar.getSimPhoneAccountsOfCurrentUser());
+
+        // Set up the SIM call manager app + carrier configs
+        ComponentName simCallManagerComponent =
+                new ComponentName("com.carrier.app", "CarrierConnectionService");
+        PhoneAccountHandle simCallManagerHandle =
+                makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager");
+        setSimCallManagerCarrierConfig(
+                1, new ComponentName("com.other.carrier", "OtherConnectionService"));
+        setSimCallManagerCarrierConfig(2, simCallManagerComponent);
+
+        // Since SIM 1 names another app, so we only get the handle for SIM 2
+        assertEquals(
+                List.of(sim2Handle),
+                mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle));
+        // We do exact component matching, not just package name matching
+        assertEquals(
+                List.of(),
+                mRegistrar.getSimPhoneAccountsFromSimCallManager(
+                        makeQuickAccountHandle(
+                                new ComponentName("com.carrier.app", "SomeOtherUnrelatedService"),
+                                "same-pkg-but-diff-svc")));
+
+        // Results are identical after we register the PhoneAccount
+        mComponentContextFixture.addConnectionService(
+                simCallManagerComponent, Mockito.mock(IConnectionService.class));
+        PhoneAccount simCallManagerAccount =
+                new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager")
+                        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                        .build();
+        mRegistrar.registerPhoneAccount(simCallManagerAccount);
+        assertEquals(
+                List.of(sim2Handle),
+                mRegistrar.getSimPhoneAccountsFromSimCallManager(simCallManagerHandle));
+    }
+
+    @Test
+    public void testMaybeNotifyTelephonyForVoiceServiceState() throws Exception {
+        // Register the SIM PhoneAccounts
+        mComponentContextFixture.addConnectionService(
+                makeQuickConnectionServiceComponentName(), Mockito.mock(IConnectionService.class));
+        PhoneAccount sim1Account = makeQuickSimAccount(1);
+        registerAndEnableAccount(sim1Account);
+        PhoneAccount sim2Account = makeQuickSimAccount(2);
+        registerAndEnableAccount(sim2Account);
+        // Telephony is notified by default when new SIM accounts are registered
+        verify(mComponentContextFixture.getTelephonyManager(), times(2))
+                .setVoiceServiceStateOverride(false);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Set up the SIM call manager app + carrier configs
+        ComponentName simCallManagerComponent =
+                new ComponentName("com.carrier.app", "CarrierConnectionService");
+        PhoneAccountHandle simCallManagerHandle =
+                makeQuickAccountHandle(simCallManagerComponent, "sim-call-manager");
+        mComponentContextFixture.addConnectionService(
+                simCallManagerComponent, Mockito.mock(IConnectionService.class));
+        setSimCallManagerCarrierConfig(1, simCallManagerComponent);
+        setSimCallManagerCarrierConfig(2, simCallManagerComponent);
+
+        // When the SIM call manager is registered without the SUPPORTS capability, telephony is
+        // still notified for consistency (e.g. runtime capability removal + re-registration).
+        PhoneAccount simCallManagerAccount =
+                new PhoneAccount.Builder(simCallManagerHandle, "SIM call manager")
+                        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                        .build();
+        mRegistrar.registerPhoneAccount(simCallManagerAccount);
+        verify(mComponentContextFixture.getTelephonyManager(), times(2))
+                .setVoiceServiceStateOverride(false);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Adding the SUPPORTS capability causes the SIMs to get notified with false again for
+        // consistency purposes
+        simCallManagerAccount =
+                copyPhoneAccountAndAddCapabilities(
+                        simCallManagerAccount,
+                        PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS);
+        mRegistrar.registerPhoneAccount(simCallManagerAccount);
+        verify(mComponentContextFixture.getTelephonyManager(), times(2))
+                .setVoiceServiceStateOverride(false);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Adding the AVAILABLE capability updates the SIMs again, this time with hasService = true
+        simCallManagerAccount =
+                copyPhoneAccountAndAddCapabilities(
+                        simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE);
+        mRegistrar.registerPhoneAccount(simCallManagerAccount);
+        verify(mComponentContextFixture.getTelephonyManager(), times(2))
+                .setVoiceServiceStateOverride(true);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Removing a SIM account does nothing, regardless of SIM call manager capabilities
+        mRegistrar.unregisterPhoneAccount(sim1Account.getAccountHandle());
+        verify(mComponentContextFixture.getTelephonyManager(), never())
+                .setVoiceServiceStateOverride(anyBoolean());
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Adding a SIM account while a SIM call manager with both capabilities is registered causes
+        // a call to telephony with hasService = true
+        mRegistrar.registerPhoneAccount(sim1Account);
+        verify(mComponentContextFixture.getTelephonyManager(), times(1))
+                .setVoiceServiceStateOverride(true);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Removing the SIM call manager while it has both capabilities causes a call to telephony
+        // with hasService = false
+        mRegistrar.unregisterPhoneAccount(simCallManagerHandle);
+        verify(mComponentContextFixture.getTelephonyManager(), times(2))
+                .setVoiceServiceStateOverride(false);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Removing the SIM call manager while it has the SUPPORTS capability but not AVAILABLE
+        // still causes a call to telephony with hasService = false for consistency
+        simCallManagerAccount =
+                copyPhoneAccountAndRemoveCapabilities(
+                        simCallManagerAccount, PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE);
+        mRegistrar.registerPhoneAccount(simCallManagerAccount);
+        clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration
+        mRegistrar.unregisterPhoneAccount(simCallManagerHandle);
+        verify(mComponentContextFixture.getTelephonyManager(), times(2))
+                .setVoiceServiceStateOverride(false);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+
+        // Finally, removing the SIM call manager while it has neither capability still causes a
+        // call to telephony with hasService = false for consistency
+        simCallManagerAccount =
+                copyPhoneAccountAndRemoveCapabilities(
+                        simCallManagerAccount,
+                        PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS);
+        mRegistrar.registerPhoneAccount(simCallManagerAccount);
+        clearInvocations(mComponentContextFixture.getTelephonyManager()); // from re-registration
+        mRegistrar.unregisterPhoneAccount(simCallManagerHandle);
+        verify(mComponentContextFixture.getTelephonyManager(), times(2))
+                .setVoiceServiceStateOverride(false);
+        clearInvocations(mComponentContextFixture.getTelephonyManager());
+    }
+
+    /**
+     * Test PhoneAccountHandle Migration Logic.
+     */
+    @Test
+    public void testPhoneAccountMigration() throws Exception {
+        PhoneAccountRegistrar.State testState = makeQuickStateWithTelephonyPhoneAccountHandle();
+        final int mTestPhoneAccountHandleSubIdInt = 123;
+        // Mock SubscriptionManager
+        SubscriptionInfo subscriptionInfo = new SubscriptionInfo(
+                mTestPhoneAccountHandleSubIdInt, "id0", 1, "a", "b", 1, 1, "test",
+                        1, null, null, null, null, false, null, null);
+        List<SubscriptionInfo> subscriptionInfoList = new ArrayList<>();
+        subscriptionInfoList.add(subscriptionInfo);
+        when(mSubscriptionManager.getAllSubscriptionInfoList()).thenReturn(subscriptionInfoList);
+        mRegistrar.migratePhoneAccountHandle(testState);
+        Collection<DefaultPhoneAccountHandle> defaultPhoneAccountHandles
+                = testState.defaultOutgoingAccountHandles.values();
+        DefaultPhoneAccountHandle defaultPhoneAccountHandle
+                = defaultPhoneAccountHandles.iterator().next();
+        assertEquals(Integer.toString(mTestPhoneAccountHandleSubIdInt),
+                defaultPhoneAccountHandle.phoneAccountHandle.getId());
+    }
+
     private static ComponentName makeQuickConnectionServiceComponentName() {
         return new ComponentName(
                 "com.android.server.telecom.tests",
@@ -1086,6 +1274,23 @@
                 "label" + idx);
     }
 
+    private static PhoneAccount copyPhoneAccountAndOverrideCapabilities(
+            PhoneAccount base, int newCapabilities) {
+        return base.toBuilder().setCapabilities(newCapabilities).build();
+    }
+
+    private static PhoneAccount copyPhoneAccountAndAddCapabilities(
+            PhoneAccount base, int capabilitiesToAdd) {
+        return copyPhoneAccountAndOverrideCapabilities(
+                base, base.getCapabilities() | capabilitiesToAdd);
+    }
+
+    private static PhoneAccount copyPhoneAccountAndRemoveCapabilities(
+            PhoneAccount base, int capabilitiesToRemove) {
+        return copyPhoneAccountAndOverrideCapabilities(
+                base, base.getCapabilities() & ~capabilitiesToRemove);
+    }
+
     private PhoneAccount makeQuickAccount(String id, int idx) {
         return makeQuickAccountBuilder(id, idx)
                 .setAddress(Uri.parse("http://foo.com/" + idx))
@@ -1098,6 +1303,44 @@
                 .build();
     }
 
+    /**
+     * Similar to {@link #makeQuickAccount}, but also hooks up {@code TelephonyManager} so that it
+     * returns {@code simId} as the account's subscriptionId.
+     */
+    private PhoneAccount makeQuickSimAccount(int simId) {
+        PhoneAccount simAccount =
+                makeQuickAccountBuilder("sim" + simId, simId)
+                        .setCapabilities(
+                                PhoneAccount.CAPABILITY_CALL_PROVIDER
+                                        | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                        .setIsEnabled(true)
+                        .build();
+        when(mComponentContextFixture
+                        .getTelephonyManager()
+                        .getSubscriptionId(simAccount.getAccountHandle()))
+                .thenReturn(simId);
+        // mComponentContextFixture already sets up the createForSubscriptionId self-reference
+        when(mComponentContextFixture
+                        .getTelephonyManager()
+                        .createForPhoneAccountHandle(simAccount.getAccountHandle()))
+                .thenReturn(mComponentContextFixture.getTelephonyManager());
+        return simAccount;
+    }
+
+    /**
+     * Hooks up carrier config to point to {@code simCallManagerComponent} for the given {@code
+     * subscriptionId}.
+     */
+    private void setSimCallManagerCarrierConfig(
+            int subscriptionId, @Nullable ComponentName simCallManagerComponent) {
+        PersistableBundle config = new PersistableBundle();
+        config.putString(
+                CarrierConfigManager.KEY_DEFAULT_SIM_CALL_MANAGER_STRING,
+                simCallManagerComponent != null ? simCallManagerComponent.flattenToString() : null);
+        when(mComponentContextFixture.getCarrierConfigManager().getConfigForSubId(subscriptionId))
+                .thenReturn(config);
+    }
+
     private static void roundTripPhoneAccount(PhoneAccount original) throws Exception {
         PhoneAccount copy = null;
 
@@ -1232,6 +1475,25 @@
         }
     }
 
+    private PhoneAccountRegistrar.State makeQuickStateWithTelephonyPhoneAccountHandle() {
+        PhoneAccountRegistrar.State s = new PhoneAccountRegistrar.State();
+        s.accounts.add(makeQuickAccount("id0", 0));
+        s.accounts.add(makeQuickAccount("id1", 1));
+        s.accounts.add(makeQuickAccount("id2", 2));
+        PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(new ComponentName(
+                "com.android.phone",
+                        "com.android.services.telephony.TelephonyConnectionService"), "id0");
+        UserHandle userHandle = phoneAccountHandle.getUserHandle();
+        when(UserManager.get(mContext).getSerialNumberForUser(userHandle))
+            .thenReturn(0L);
+        when(UserManager.get(mContext).getUserForSerialNumber(0L))
+            .thenReturn(userHandle);
+        s.defaultOutgoingAccountHandles
+            .put(userHandle, new DefaultPhoneAccountHandle(userHandle, phoneAccountHandle,
+                "testGroup"));
+        return s;
+    }
+
     private PhoneAccountRegistrar.State makeQuickState() {
         PhoneAccountRegistrar.State s = new PhoneAccountRegistrar.State();
         s.accounts.add(makeQuickAccount("id0", 0));
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 00456e2..59a5f02 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -24,8 +24,8 @@
 import android.media.VolumeShaper;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Looper;
 import android.os.Parcel;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.telecom.TelecomManager;
@@ -47,8 +47,6 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Mock;
 import org.mockito.Spy;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -177,7 +175,7 @@
         verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -185,7 +183,7 @@
     public void testNoActionWithExternalRinger() {
         mFuture.complete(false); // not using audio coupled haptics
         Bundle externalRingerExtra = new Bundle();
-        externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_EXTERNAL_RINGER, true);
+        externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, true);
         when(mockCall1.getIntentExtras()).thenReturn(externalRingerExtra);
         when(mockCall2.getIntentExtras()).thenReturn(externalRingerExtra);
         // Start call waiting to make sure that it doesn't stop when we start ringing
@@ -195,7 +193,7 @@
         verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -228,7 +226,7 @@
         verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -244,7 +242,7 @@
         verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -263,7 +261,7 @@
         verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -280,7 +278,7 @@
         verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
                 eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
-                any(AudioAttributes.class));
+                any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -299,7 +297,7 @@
                 eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
-                any(AudioAttributes.class));
+                any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -320,7 +318,7 @@
                 eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
-                any(AudioAttributes.class));
+                any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -341,7 +339,7 @@
                 eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
         // Skip vibration for audio coupled haptics
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
-                any(AudioAttributes.class));
+                any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -357,7 +355,7 @@
         verify(mockRingtonePlayer).play(nullable(RingtoneFactory.class), nullable(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
-                nullable(AudioAttributes.class));
+                nullable(VibrationAttributes.class));
         // Simulate something stopping the ringer
         mRingerUnderTest.stopRinging();
         verify(mockRingtonePlayer).stop();
@@ -366,7 +364,7 @@
         mFuture.complete(false);
         // Then make sure that we don't actually start vibrating.
         verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
-                nullable(AudioAttributes.class));
+                nullable(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -385,7 +383,7 @@
         verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
                 eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
         verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
-                any(AudioAttributes.class));
+                any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -401,7 +399,7 @@
         verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
                 eq(true) /* isRingerAudible */, eq(false) /* isVibrationEnabled */);
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -436,7 +434,7 @@
         verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
@@ -454,7 +452,7 @@
         verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
                 nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     private void ensureRingerIsAudible() {
@@ -466,15 +464,15 @@
 
     private void enableVibrationWhenRinging() {
         when(mockVibrator.hasVibrator()).thenReturn(true);
-        when(mockSystemSettingsUtil.canVibrateWhenRinging(any(Context.class))).thenReturn(true);
+        when(mockSystemSettingsUtil.isRingVibrationEnabled(any(Context.class))).thenReturn(true);
     }
 
     private void enableVibrationOnlyWhenNotRinging() {
         when(mockVibrator.hasVibrator()).thenReturn(true);
-        when(mockSystemSettingsUtil.canVibrateWhenRinging(any(Context.class))).thenReturn(false);
+        when(mockSystemSettingsUtil.isRingVibrationEnabled(any(Context.class))).thenReturn(false);
     }
 
     private void enableRampingRinger() {
-        when(mockSystemSettingsUtil.applyRampingRinger(any(Context.class))).thenReturn(true);
+        when(mockSystemSettingsUtil.isRampingRingerEnabled(any(Context.class))).thenReturn(true);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 5c131c8..210f80e 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -199,10 +199,6 @@
         super.setUp();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
 
-        TelephonyManager mockTelephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        when(mockTelephonyManager.isVoiceCapable()).thenReturn(true);
-
         doReturn(mContext).when(mContext).getApplicationContext();
         doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
         doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
@@ -516,6 +512,25 @@
         doReturn(PackageManager.PERMISSION_DENIED)
                 .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
         PackageManager pm = mContext.getPackageManager();
+        when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(true);
+
+        registerPhoneAccountTestHelper(phoneAccount, true);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegisterPhoneAccountWithOldFeatureFlag() throws RemoteException {
+        // tests the case where the package does not have MODIFY_PHONE_STATE but is
+        // registering its own phone account as a third-party connection service
+        String packageNameToUse = "com.thirdparty.connectionservice";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "asdf", Binder.getCallingUserHandle());
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).build();
+
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+        PackageManager pm = mContext.getPackageManager();
+        when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
         when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(true);
 
         registerPhoneAccountTestHelper(phoneAccount, true);
@@ -534,7 +549,7 @@
         doReturn(PackageManager.PERMISSION_DENIED)
                 .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
         PackageManager pm = mContext.getPackageManager();
-        when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(false);
+        when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
 
         registerPhoneAccountTestHelper(phoneAccount, false);
     }
@@ -623,7 +638,7 @@
         doReturn(PackageManager.PERMISSION_DENIED)
                 .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
         PackageManager pm = mContext.getPackageManager();
-        when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(false);
+        when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
 
         try {
             mTSIBinder.unregisterPhoneAccount(phHandle);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 51e1d35..d6ff196 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -573,9 +573,6 @@
                 com.android.server.telecom.R.string.incall_default_class,
                 mInCallServiceComponentNameX.getClassName());
 
-        doReturn(true).when(mComponentContextFixture.getTelephonyManager())
-                .isVoiceCapable();
-
         mInCallServiceFixtureX = new InCallServiceFixture();
         mInCallServiceFixtureY = new InCallServiceFixture();
 
@@ -606,8 +603,8 @@
                     doReturn(args[0]).when(fakeAudioManager).isMicrophoneMute();
                     return null;
                 }
-            }).when(audioService)
-                    .setMicrophoneMute(any(Boolean.class), any(String.class), any(Integer.class));
+            }).when(audioService).setMicrophoneMute(any(Boolean.class), any(String.class),
+                    any(Integer.class), nullable(String.class));
 
         } catch (android.os.RemoteException e) {
             // Do nothing, leave the faked microphone state as-is
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index 264e087..5353bc6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -40,6 +40,7 @@
     public void setUp() throws Exception {
         Log.setTag(TESTING_TAG);
         Log.setIsExtendedLoggingEnabled(true);
+        Log.setUnitTestingEnabled(true);
         mMockitoHelper.setUp(InstrumentationRegistry.getContext(), getClass());
         mComponentContextFixture = new ComponentContextFixture();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();