Merge "update telecom test apps directory (NPE & RoleManager)" into tm-dev
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index eea3a6f..be67223 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -73,7 +73,7 @@
     <string name="non_primary_user" msgid="315564589279622098">"Alleen de eigenaar van het apparaat kan geblokkeerd nummers bekijken en beheren."</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"Blokkering opheffen"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Blokkering tijdelijk uitgezet"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Als je een noodnummer belt of er een sms naartoe stuurt, wordt de blokkering uitgeschakeld om te zorgen dat hulpdiensten contact met je kunnen opnemen."</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Als je een noodnummer belt of er een sms naartoe stuurt, wordt de blokkering uitgezet om te zorgen dat hulpdiensten contact met je kunnen opnemen."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Nu opnieuw aanzetten"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> geblokkeerd"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"Blokkering van <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> opgeheven"</string>
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f98513e..9d6dd07 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -3300,7 +3300,7 @@
      *
      * @return {@code True} if there are any non-external calls, {@code false} otherwise.
      */
-    boolean hasAnyCalls() {
+    public boolean hasAnyCalls() {
         if (mCalls.isEmpty()) {
             return false;
         }
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 c78c8d5..6e952e3 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -21,6 +21,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.compat.CompatChanges;
 import android.app.Notification;
@@ -90,17 +91,6 @@
         AppOpsManager.OnOpActiveChangedListener {
     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
@@ -1622,27 +1612,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 */);
 
-        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);
-        }
+        Log.d(this, "getDefaultDialerComponent: defaultPhoneAppComponent=[%s]",
+                defaultPhoneAppComponent);
 
-        return defaultDialerComponent;
+        // 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/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index f53f239..f8f0c49 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;
@@ -263,13 +268,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/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 4c23f81..64659eb 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -56,9 +56,9 @@
     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
          put(NEW_DEVICE_CONNECTED, "NEW_DEVICE_CONNECTED");
          put(LOST_DEVICE, "LOST_DEVICE");
-         put(CONNECT_HFP, "CONNECT_HFP");
-         put(DISCONNECT_HFP, "DISCONNECT_HFP");
-         put(RETRY_HFP_CONNECTION, "RETRY_HFP_CONNECTION");
+         put(CONNECT_BT, "CONNECT_BT");
+         put(DISCONNECT_BT, "DISCONNECT_BT");
+         put(RETRY_BT_CONNECTION, "RETRY_BT_CONNECTION");
          put(BT_AUDIO_IS_ON, "BT_AUDIO_IS_ON");
          put(BT_AUDIO_LOST, "BT_AUDIO_LOST");
          put(CONNECTION_TIMEOUT, "CONNECTION_TIMEOUT");
@@ -98,11 +98,11 @@
     public static final int LOST_DEVICE = 2;
 
     // arg2 (optional): the address of the specific device to connect to.
-    public static final int CONNECT_HFP = 100;
+    public static final int CONNECT_BT = 100;
     // No args.
-    public static final int DISCONNECT_HFP = 101;
+    public static final int DISCONNECT_BT = 101;
     // arg2: the address of the device to connect to.
-    public static final int RETRY_HFP_CONNECTION = 102;
+    public static final int RETRY_BT_CONNECTION = 102;
 
     // arg2: the address of the device that is on
     public static final int BT_AUDIO_IS_ON = 200;
@@ -159,29 +159,29 @@
                     case LOST_DEVICE:
                         removeDevice((String) args.arg2);
                         break;
-                    case CONNECT_HFP:
+                    case CONNECT_BT:
                         String actualAddress = connectBtAudio((String) args.arg2,
                             false /* switchingBtDevices*/);
 
                         if (actualAddress != null) {
                             transitionTo(getConnectingStateForAddress(actualAddress,
-                                    "AudioOff/CONNECT_HFP"));
+                                    "AudioOff/CONNECT_BT"));
                         } else {
                             Log.w(LOG_TAG, "Tried to connect to %s but failed to connect to" +
-                                    " any HFP device.", (String) args.arg2);
+                                    " any BT device.", (String) args.arg2);
                         }
                         break;
-                    case DISCONNECT_HFP:
+                    case DISCONNECT_BT:
                         // Ignore.
                         break;
-                    case RETRY_HFP_CONNECTION:
-                        Log.i(LOG_TAG, "Retrying HFP connection to %s", (String) args.arg2);
+                    case RETRY_BT_CONNECTION:
+                        Log.i(LOG_TAG, "Retrying BT connection to %s", (String) args.arg2);
                         String retryAddress = connectBtAudio((String) args.arg2, args.argi1,
                             false /* switchingBtDevices*/);
 
                         if (retryAddress != null) {
                             transitionTo(getConnectingStateForAddress(retryAddress,
-                                    "AudioOff/RETRY_HFP_CONNECTION"));
+                                    "AudioOff/RETRY_BT_CONNECTION"));
                         } else {
                             Log.i(LOG_TAG, "Retry failed.");
                         }
@@ -191,12 +191,12 @@
                         break;
                     case BT_AUDIO_IS_ON:
                         String address = (String) args.arg2;
-                        Log.w(LOG_TAG, "HFP audio unexpectedly turned on from device %s", address);
+                        Log.w(LOG_TAG, "BT audio unexpectedly turned on from device %s", address);
                         transitionTo(getConnectedStateForAddress(address,
                                 "AudioOff/BT_AUDIO_IS_ON"));
                         break;
                     case BT_AUDIO_LOST:
-                        Log.i(LOG_TAG, "Received HFP off for device %s while HFP off.",
+                        Log.i(LOG_TAG, "Received BT off for device %s while BT off.",
                                 (String) args.arg2);
                         mListener.onUnexpectedBluetoothStateChange();
                         break;
@@ -262,7 +262,7 @@
                             transitionToActualState();
                         }
                         break;
-                    case CONNECT_HFP:
+                    case CONNECT_BT:
                         if (!switchingBtDevices) {
                             // Ignore repeated connection attempts to the same device
                             break;
@@ -272,16 +272,16 @@
                             true /* switchingBtDevices*/);
                         if (actualAddress != null) {
                             transitionTo(getConnectingStateForAddress(actualAddress,
-                                    "AudioConnecting/CONNECT_HFP"));
+                                    "AudioConnecting/CONNECT_BT"));
                         } else {
                             Log.w(LOG_TAG, "Tried to connect to %s but failed" +
-                                    " to connect to any HFP device.", (String) args.arg2);
+                                    " to connect to any BT device.", (String) args.arg2);
                         }
                         break;
-                    case DISCONNECT_HFP:
+                    case DISCONNECT_BT:
                         mDeviceManager.disconnectAudio();
                         break;
-                    case RETRY_HFP_CONNECTION:
+                    case RETRY_BT_CONNECTION:
                         if (!switchingBtDevices) {
                             Log.d(LOG_TAG, "Retry message came through while connecting.");
                             break;
@@ -291,7 +291,7 @@
                             true /* switchingBtDevices*/);
                         if (retryAddress != null) {
                             transitionTo(getConnectingStateForAddress(retryAddress,
-                                    "AudioConnecting/RETRY_HFP_CONNECTION"));
+                                    "AudioConnecting/RETRY_BT_CONNECTION"));
                         } else {
                             Log.i(LOG_TAG, "Retry failed.");
                         }
@@ -318,7 +318,7 @@
                                     mDeviceAddress);
                             transitionToActualState();
                         } else {
-                            Log.w(LOG_TAG, "Got HFP lost message for device %s while" +
+                            Log.w(LOG_TAG, "Got BT lost message for device %s while" +
                                     " connecting to %s.", address, mDeviceAddress);
                             mListener.onUnexpectedBluetoothStateChange();
                         }
@@ -351,7 +351,7 @@
         public void enter() {
             // Remove any of the retries that are still in the queue once any device becomes
             // connected.
-            removeMessages(RETRY_HFP_CONNECTION);
+            removeMessages(RETRY_BT_CONNECTION);
             // Remove and add to ensure that the device is at the top.
             mMostRecentlyUsedDevices.remove(mDeviceAddress);
             mMostRecentlyUsedDevices.add(mDeviceAddress);
@@ -379,7 +379,7 @@
                             transitionToActualState();
                         }
                         break;
-                    case CONNECT_HFP:
+                    case CONNECT_BT:
                         if (!switchingBtDevices) {
                             // Ignore connection to already connected device.
                             break;
@@ -389,16 +389,16 @@
                             true /* switchingBtDevices*/);
                         if (actualAddress != null) {
                             transitionTo(getConnectingStateForAddress(address,
-                                    "AudioConnected/CONNECT_HFP"));
+                                    "AudioConnected/CONNECT_BT"));
                         } else {
                             Log.w(LOG_TAG, "Tried to connect to %s but failed" +
-                                    " to connect to any HFP device.", (String) args.arg2);
+                                    " to connect to any BT device.", (String) args.arg2);
                         }
                         break;
-                    case DISCONNECT_HFP:
+                    case DISCONNECT_BT:
                         mDeviceManager.disconnectAudio();
                         break;
-                    case RETRY_HFP_CONNECTION:
+                    case RETRY_BT_CONNECTION:
                         if (!switchingBtDevices) {
                             Log.d(LOG_TAG, "Retry message came through while connected.");
                             break;
@@ -408,7 +408,7 @@
                             true /* switchingBtDevices*/);
                         if (retryAddress != null) {
                             transitionTo(getConnectingStateForAddress(retryAddress,
-                                    "AudioConnected/RETRY_HFP_CONNECTION"));
+                                    "AudioConnected/RETRY_BT_CONNECTION"));
                         } else {
                             Log.i(LOG_TAG, "Retry failed.");
                         }
@@ -429,10 +429,10 @@
                         break;
                     case BT_AUDIO_LOST:
                         if (Objects.equals(mDeviceAddress, address) || address == null) {
-                            Log.i(LOG_TAG, "HFP connection with device %s lost.", mDeviceAddress);
+                            Log.i(LOG_TAG, "BT connection with device %s lost.", mDeviceAddress);
                             transitionToActualState();
                         } else {
-                            Log.w(LOG_TAG, "Got HFP lost message for device %s while" +
+                            Log.w(LOG_TAG, "Got BT lost message for device %s while" +
                                     " connected to %s.", address, mDeviceAddress);
                             mListener.onUnexpectedBluetoothStateChange();
                         }
@@ -461,7 +461,7 @@
 
     private BluetoothStateListener mListener;
     private BluetoothDeviceManager mDeviceManager;
-    // Tracks the active devices in the BT stack (HFP or hearing aid).
+    // Tracks the active devices in the BT stack (HFP or hearing aid or le audio).
     private BluetoothDevice mHfpActiveDeviceCache = null;
     private BluetoothDevice mHearingAidActiveDeviceCache = null;
     private BluetoothDevice mLeAudioActiveDeviceCache = null;
@@ -505,7 +505,7 @@
     }
 
     /**
-     * Returns whether there is a HFP device available to route audio to.
+     * Returns whether there is a BT device available to route audio to.
      * @return true if there is a device, false otherwise.
      */
     public boolean isBluetoothAvailable() {
@@ -550,16 +550,16 @@
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = Log.createSubsession();
         args.arg2 = address;
-        sendMessage(CONNECT_HFP, args);
+        sendMessage(CONNECT_BT, args);
     }
 
     /**
-     * Disconnects Bluetooth HFP audio.
+     * Disconnects Bluetooth audio.
      */
     public void disconnectBluetoothAudio() {
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = Log.createSubsession();
-        sendMessage(DISCONNECT_HFP, args);
+        sendMessage(DISCONNECT_BT, args);
     }
 
     public void disconnectAudio() {
@@ -708,7 +708,7 @@
                 args.arg1 = Log.createSubsession();
                 args.arg2 = actualAddress;
                 args.argi1 = retryCount + 1;
-                sendMessageDelayed(RETRY_HFP_CONNECTION, args,
+                sendMessageDelayed(RETRY_BT_CONNECTION, args,
                         mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                                 mContext.getContentResolver()));
             }
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index f04537a..d1d48ce 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -85,14 +85,14 @@
 
     @SmallTest
     @Test
-    public void testConnectHfpRetryWhileNotConnected() {
+    public void testConnectBtRetryWhileNotConnected() {
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
         setupConnectedDevices(new BluetoothDevice[]{DEVICE1}, null, null, null, null, null);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(0L);
         when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
-        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE1.getAddress());
+        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, DEVICE1.getAddress());
         // Wait 3 times: for the first connection attempt, the retry attempt,
         // the second retry, and once more to make sure there are only three attempts.
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -146,7 +146,7 @@
 
     @SmallTest
     @Test
-    public void testConnectHfpRetryWhileConnectedToAnotherDevice() {
+    public void testConnectBtRetryWhileConnectedToAnotherDevice() {
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
         setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null, null,
@@ -154,7 +154,7 @@
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(0L);
         when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
-        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_HFP, DEVICE2.getAddress());
+        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, DEVICE2.getAddress());
         // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
         // so wait twice for the retry attempt, again to make sure there are only three attempts,
         // and once more for good luck.
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index 1a3112a..d923c90 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -440,7 +440,7 @@
                 .setInitialDevice(null)
                 .setConnectedDevices(DEVICE2, DEVICE1)
                 .setActiveDevice(DEVICE1)
-                .setMessageType(BluetoothRouteManager.CONNECT_HFP)
+                .setMessageType(BluetoothRouteManager.CONNECT_BT)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(CONNECT)
                 .setExpectedConnectionDevice(DEVICE1)
@@ -464,7 +464,7 @@
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Device loses HFP audio but remains connected. No fallback.")
+                .setName("Device loses BT audio but remains connected. No fallback.")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2)
@@ -477,7 +477,7 @@
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Device loses HFP audio but remains connected."
+                .setName("Device loses BT audio but remains connected."
                         + " No fallback even though other devices available.")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
@@ -495,7 +495,7 @@
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
-                .setMessageType(BluetoothRouteManager.CONNECT_HFP)
+                .setMessageType(BluetoothRouteManager.CONNECT_BT)
                 .setMessageDevice(DEVICE3)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(CONNECT_SWITCH_DEVICE)
@@ -509,7 +509,7 @@
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
-                .setMessageType(BluetoothRouteManager.CONNECT_HFP)
+                .setMessageType(BluetoothRouteManager.CONNECT_BT)
                 .setMessageDevice(DEVICE3)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(CONNECT_SWITCH_DEVICE)
@@ -628,22 +628,22 @@
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Audio routing requests HFP disconnection while a device is active")
+                .setName("Audio routing requests BT disconnection while a device is active")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE3)
-                .setMessageType(BluetoothRouteManager.DISCONNECT_HFP)
+                .setMessageType(BluetoothRouteManager.DISCONNECT_BT)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
                 .setExpectedBluetoothInteraction(DISCONNECT)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
-                .setName("Audio routing requests HFP disconnection while a device is pending")
+                .setName("Audio routing requests BT disconnection while a device is pending")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE3)
-                .setMessageType(BluetoothRouteManager.DISCONNECT_HFP)
+                .setMessageType(BluetoothRouteManager.DISCONNECT_BT)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
                 .setExpectedBluetoothInteraction(DISCONNECT)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
@@ -683,7 +683,7 @@
                 .setActiveDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE3)
                 .setHearingAidBtDevices(Collections.singletonList(DEVICE2))
-                .setMessageType(BluetoothRouteManager.CONNECT_HFP)
+                .setMessageType(BluetoothRouteManager.CONNECT_BT)
                 .setMessageDevice(DEVICE2)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(NONE)
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 dfc41a2..ddacf43 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -65,6 +65,7 @@
 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;
@@ -917,13 +918,10 @@
 
    /**
      * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
-     * 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.
+     * supports third party app.
      */
     @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)
@@ -967,9 +965,6 @@
             // 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 sent by NotificationManager
-            verify(mNotificationManager, times(1)).notify(eq(InCallController.NOTIFICATION_TAG),
-                    eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any());
         } finally {
             mockitoSession.finishMocking();
         }
@@ -1017,67 +1012,6 @@
                 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 {
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);
+    }
+}