Merge "Correct HeadsetMediaButton behavior for external calls." into tm-dev
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/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 6a7261e..6a1f5aa 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -537,9 +537,9 @@
         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:");
@@ -557,6 +557,7 @@
 
     @VisibleForTesting
     public void setIsTonePlaying(boolean isTonePlaying) {
+        Log.i(this, "setIsTonePlaying; isTonePlaying=%b", isTonePlaying);
         mIsTonePlaying = isTonePlaying;
         mCallAudioModeStateMachine.sendMessageWithArgs(
                 isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
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/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 1e8f779..c78c8d5 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -22,8 +22,11 @@
 import android.Manifest;
 import android.annotation.NonNull;
 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 +40,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;
@@ -87,6 +91,16 @@
     public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
     public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
 
+    /**
+     * Enable a crash notification if the default dialer app does not implement the
+     * {@link InCallService} and the system Dialer takes over.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ENABLE_NOTIFICATION_FOR_DEFAULT_DIALER_CRASH = 218903401L; // bug id
+
     public class InCallServiceConnection {
         /**
          * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
@@ -1620,10 +1634,14 @@
                         true /* ignoreDisabled */)
                         : getInCallServiceComponent(packageName,
                                 IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */);
-        if (packageName != null && defaultDialerComponent == null) {
+
+        if (packageName != null && defaultDialerComponent == null &&
+                CompatChanges.isChangeEnabled(ENABLE_NOTIFICATION_FOR_DEFAULT_DIALER_CRASH,
+                        Binder.getCallingUid())) {
             // The in call service of default phone app is disabled, send notification.
             sendCrashedInCallServiceNotification(packageName);
         }
+
         return defaultDialerComponent;
     }
 
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 524d119..5a514ce 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -438,7 +438,8 @@
             mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
                 @Override
                 public void onCompletion(MediaPlayer mp) {
-                    Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
+                    Log.i(InCallTonePlayer.this, "playMediaTone: toneResourceId=%d completed.",
+                            toneResourceId);
                     synchronized (InCallTonePlayer.this) {
                         mState = STATE_OFF;
                     }
@@ -508,14 +509,26 @@
     }
 
     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);
+                    Log.wtf(InCallTonePlayer.this,
+                            "cleanUpTonePlayer(): Over-releasing focus for tone player.");
+                } else if (--sTonesPlaying == 0) {
+                    Log.i(InCallTonePlayer.this,
+                            "cleanUpTonePlayer(): tonesPlaying=%d, tone completed", sTonesPlaying);
+                    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", sTonesPlaying);
                 }
             }
         }.prepare());
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index dfcbbc4..e65a651 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -166,6 +166,7 @@
     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;
@@ -195,6 +196,7 @@
         mUserManager = UserManager.get(context);
         mDefaultDialerCache = defaultDialerCache;
         mSubscriptionManager = SubscriptionManager.from(mContext);
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mAppLabelProxy = appLabelProxy;
         mCurrentUserHandle = Process.myUserHandle();
 
@@ -217,9 +219,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;
     }
@@ -1152,11 +1152,10 @@
                 "Notifying telephony of voice service override change for %d SIMs, hasService = %b",
                 simHandlesToNotify.size(),
                 hasService);
-        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
         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 = tm.createForPhoneAccountHandle(simHandle);
+            TelephonyManager simTm = mTelephonyManager.createForPhoneAccountHandle(simHandle);
             if (simTm == null) continue;
             simTm.setVoiceServiceStateOverride(hasService);
         }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 228a489..2785739 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -21,6 +21,7 @@
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeAudioCodecStatus;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
@@ -34,6 +35,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.concurrent.Executor;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
@@ -47,6 +49,37 @@
     public static final int DEVICE_TYPE_HEARING_AID = 1;
     public static final int DEVICE_TYPE_LE_AUDIO = 2;
 
+    private BluetoothLeAudio.Callback mLeAudioCallbacks =
+        new BluetoothLeAudio.Callback() {
+            @Override
+            public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {}
+            @Override
+            public void onGroupStatusChanged(int groupId, int groupStatus) {}
+            @Override
+            public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
+                Log.i(this, device.getAddress() + " group added " + groupId);
+                if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
+                    Log.w(this, "invalid parameter");
+                    return;
+                }
+
+                synchronized (mLock) {
+                    mGroupsByDevice.put(device, groupId);
+                }
+            }
+            @Override
+            public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
+                if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
+                    Log.w(this, "invalid parameter");
+                    return;
+                }
+
+                synchronized (mLock) {
+                    mGroupsByDevice.remove(device);
+                }
+            }
+        };
+
     private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
             new BluetoothProfile.ServiceListener() {
                 @Override
@@ -66,6 +99,11 @@
                                 mBluetoothLeAudioService = (BluetoothLeAudio) proxy;
                                 logString = "Got BluetoothLeAudio: "
                                         + mBluetoothLeAudioService;
+                                if (!mLeAudioCallbackRegistered) {
+                                    mBluetoothLeAudioService.registerCallback(
+                                                mExecutor, mLeAudioCallbacks);
+                                    mLeAudioCallbackRegistered = true;
+                                }
                             } else {
                                 logString = "Connected to non-requested bluetooth service." +
                                         " Not changing bluetooth headset.";
@@ -145,11 +183,13 @@
     private BluetoothRouteManager mBluetoothRouteManager;
     private BluetoothHeadset mBluetoothHeadset;
     private BluetoothHearingAid mBluetoothHearingAid;
+    private boolean mLeAudioCallbackRegistered = false;
     private BluetoothLeAudio mBluetoothLeAudioService;
     private boolean mLeAudioSetAsCommunicationDevice = false;
     private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
     private BluetoothAdapter mBluetoothAdapter;
     private AudioManager mAudioManager;
+    private Executor mExecutor;
 
     public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) {
         if (bluetoothAdapter != null) {
@@ -161,6 +201,7 @@
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
                     BluetoothProfile.LE_AUDIO);
             mAudioManager = context.getSystemService(AudioManager.class);
+            mExecutor = context.getMainExecutor();
         }
     }
 
@@ -265,6 +306,7 @@
 
     public void setLeAudioServiceForTesting(BluetoothLeAudio bluetoothLeAudio) {
         mBluetoothLeAudioService = bluetoothLeAudio;
+        mBluetoothLeAudioService.registerCallback(mExecutor, mLeAudioCallbacks);
     }
 
     public static String getDeviceTypeString(int deviceType) {
@@ -339,29 +381,6 @@
         }
     }
 
-    void onGroupNodeAdded(BluetoothDevice device, int groupId) {
-        Log.i(this, device.getAddress() + " group added " + groupId);
-        if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
-            Log.w(this, "invalid parameter");
-            return;
-        }
-
-        synchronized (mLock) {
-            mGroupsByDevice.put(device, groupId);
-        }
-    }
-
-    void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
-        if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
-            Log.w(this, "invalid parameter");
-            return;
-        }
-
-        synchronized (mLock) {
-            mGroupsByDevice.remove(device);
-        }
-    }
-
     public void disconnectAudio() {
         if (mBluetoothAdapter != null) {
             for (BluetoothDevice device: mBluetoothAdapter.getActiveDevices(
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 17da806..5c5ded0 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -46,8 +46,6 @@
         INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
         INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
-        INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED);
-        INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_STATUS_CHANGED);
     }
 
     // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
@@ -74,9 +72,6 @@
                 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
                     handleActiveDeviceChanged(intent);
                     break;
-                case BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED:
-                    handleGroupNodeStatusChanged(intent);
-                    break;
             }
         } finally {
             Log.endSession();
@@ -199,22 +194,6 @@
         }
     }
 
-    private void handleGroupNodeStatusChanged(Intent intent) {
-        BluetoothDevice device =
-                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
-        int groupId = intent.getIntExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID,
-                BluetoothLeAudio.GROUP_ID_INVALID);
-        int groupNodeStatus = intent.getIntExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_NODE_STATUS,
-                -1);
-
-        if (groupNodeStatus == BluetoothLeAudio.GROUP_NODE_ADDED) {
-            mBluetoothDeviceManager.onGroupNodeAdded(device, groupId);
-        } else {
-            mBluetoothDeviceManager.onGroupNodeRemoved(device, groupId);
-        }
-    }
-
     public BluetoothDeviceManager getBluetoothDeviceManager() {
         return mBluetoothDeviceManager;
     }
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
old mode 100644
new mode 100755
index 75c1996..ae76708
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -98,6 +98,7 @@
     private void processOutgoingCallIntent(Intent intent, String callingPackageName,
             boolean canCallNonEmergency, boolean isLocalInvocation) {
         Uri handle = intent.getData();
+        if (handle == null) return;
         String scheme = handle.getScheme();
         String uriString = handle.getSchemeSpecificPart();
 
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index b153fa4..03a5b43 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -17,7 +17,7 @@
 package com.android.server.telecom.ui;
 
 import static android.Manifest.permission.READ_PHONE_STATE;
-import static android.app.admin.DevicePolicyResources.Strings.Dialer.NOTIFICATION_MISSED_WORK_CALL_TITLE;
+import static android.app.admin.DevicePolicyResources.Strings.Telecomm.NOTIFICATION_MISSED_WORK_CALL_TITLE;
 
 import android.annotation.NonNull;
 import android.app.BroadcastOptions;
@@ -337,9 +337,9 @@
 
             CallerInfo ci = callInfo.getCallerInfo();
             if (ci != null && ci.userType == CallerInfo.USER_TYPE_WORK) {
-                titleText = mContext.getSystemService(DevicePolicyManager.class).getString(
-                        NOTIFICATION_MISSED_WORK_CALL_TITLE,
-                        () -> mContext.getString(R.string.notification_missedWorkCallTitle));
+                titleText = mContext.getSystemService(DevicePolicyManager.class).getResources()
+                        .getString(NOTIFICATION_MISSED_WORK_CALL_TITLE, () ->
+                                mContext.getString(R.string.notification_missedWorkCallTitle));
             } else {
                 titleText = mContext.getString(R.string.notification_missedCallTitle);
             }
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 29462f5..f011f0c 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -45,6 +45,7 @@
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -68,6 +69,7 @@
     BluetoothDeviceManager mBluetoothDeviceManager;
     BluetoothProfile.ServiceListener serviceListenerUnderTest;
     BluetoothStateReceiver receiverUnderTest;
+    ArgumentCaptor<BluetoothLeAudio.Callback> leAudioCallbacksTest;
 
     private BluetoothDevice device1;
     private BluetoothDevice device2;
@@ -109,7 +111,11 @@
 
         mBluetoothDeviceManager.setHeadsetServiceForTesting(mBluetoothHeadset);
         mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
+
+        leAudioCallbacksTest =
+                         ArgumentCaptor.forClass(BluetoothLeAudio.Callback.class);
         mBluetoothDeviceManager.setLeAudioServiceForTesting(mBluetoothLeAudio);
+        verify(mBluetoothLeAudio).registerCallback(any(), leAudioCallbacksTest.capture());
     }
 
     @Override
@@ -162,8 +168,7 @@
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
-        receiverUnderTest.onReceive(mContext,
-                buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);
 
         receiverUnderTest.onReceive(mContext,
@@ -172,8 +177,7 @@
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
-        receiverUnderTest.onReceive(mContext,
-                buildGroupNodeStatusChangedIntent(2, device6, BluetoothLeAudio.GROUP_NODE_ADDED));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 2);
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(2)).thenReturn(device6);
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
@@ -216,13 +220,11 @@
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
-        receiverUnderTest.onReceive(mContext,
-                buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
-        receiverUnderTest.onReceive(mContext,
-                buildGroupNodeStatusChangedIntent(1, device6, BluetoothLeAudio.GROUP_NODE_ADDED));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);
         assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
         assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
@@ -376,8 +378,7 @@
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
-        receiverUnderTest.onReceive(mContext,
-                buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
 
@@ -408,13 +409,11 @@
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
-        receiverUnderTest.onReceive(mContext,
-                buildGroupNodeStatusChangedIntent(1, device5, BluetoothLeAudio.GROUP_NODE_ADDED));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device6,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
-        receiverUnderTest.onReceive(mContext,
-                buildGroupNodeStatusChangedIntent(1, device6, BluetoothLeAudio.GROUP_NODE_ADDED));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
         mBluetoothDeviceManager.connectAudio(device5.getAddress());
@@ -457,22 +456,6 @@
         return i;
     }
 
-    private Intent buildGroupNodeStatusChangedIntent(int groupId, BluetoothDevice device,
-                int nodeStatus) {
-        Intent i = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED);
-        i.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
-        i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, groupId);
-        i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_NODE_STATUS, nodeStatus);
-        return i;
-    }
-
-    private Intent buildGroupStatusChangedIntent(int groupId, int groupStatus) {
-        Intent i = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_GROUP_STATUS_CHANGED);
-        i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_ID, groupId);
-        i.putExtra(BluetoothLeAudio.EXTRA_LE_AUDIO_GROUP_STATUS, groupStatus);
-        return i;
-    }
-
     private BluetoothDevice makeBluetoothDevice(String address) {
         Parcel p1 = Parcel.obtain();
         p1.writeString(address);
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 79032da..dfc41a2 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -64,6 +64,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -102,7 +103,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 +123,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 +144,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,10 +917,13 @@
 
    /**
      * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
-     * supports third party app
+     * supports third party app.  Also, we want to verify a notification is sent to apps targeting
+     * Tiramisu and above when the InCallService of the default app is disabled.
      */
     @MediumTest
     @Test
+    @CoreCompatChangeRule.EnableCompatChanges({
+            InCallController.ENABLE_NOTIFICATION_FOR_DEFAULT_DIALER_CRASH})
     public void testBindToService_ThirdPartyApp() throws Exception {
         final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
                 .strictness(Strictness.WARN)
@@ -926,6 +937,7 @@
 
             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
@@ -1005,6 +1017,67 @@
                 eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any());
     }
 
+    /**
+     * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
+     * supports third party app. Also, we want to verify a notification is NOT sent to apps
+     * targeting below Tiramisu when the InCallService of the default app is disabled.
+     */
+    @MediumTest
+    @Test
+    @CoreCompatChangeRule.DisableCompatChanges({
+            InCallController.ENABLE_NOTIFICATION_FOR_DEFAULT_DIALER_CRASH})
+    public void testBindToService_ThirdPartyAppBelowTiramisu() throws Exception {
+        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+                .strictness(Strictness.WARN)
+                .spyStatic(PermissionChecker.class)
+                .startMocking();
+        try {
+            setupMocks(false /* isExternalCall */);
+            setupMockPackageManager(false /* default */, false /* nonui */, true /* appop_nonui */,
+                    true /* system */, false /* external calls */, false /* self mgd in default */,
+                    false /* self mgd in car*/);
+
+            ApplicationInfo applicationInfo = new ApplicationInfo();
+            applicationInfo.targetSdkVersion = Build.VERSION_CODES.S_V2;
+            // 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(
+                            any(Context.class), eq(Manifest.permission.MANAGE_ONGOING_CALLS),
+                            anyInt(), any(AttributionSource.class), nullable(String.class)));
+
+            // Now bind; we should bind to the system dialer and app op non ui app.
+            mInCallController.bindToServices(mMockCall);
+
+            // Bind InCallServices
+            ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+            verify(mMockContext, times(2)).bindServiceAsUser(
+                    bindIntentCaptor.capture(),
+                    any(ServiceConnection.class),
+                    eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                            | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+                    eq(UserHandle.CURRENT));
+
+            // Verify bind
+            assertEquals(2, bindIntentCaptor.getAllValues().size());
+
+            // Should have first bound to the system dialer.
+            verifyBinding(bindIntentCaptor, 0, SYS_PKG, SYS_CLASS);
+
+            // Should have next bound to the third party app op non ui app.
+            verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_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());
+
+        } finally {
+            mockitoSession.finishMocking();
+        }
+    }
+
     @MediumTest
     @Test
     public void testSanitizeContactName() throws Exception {