Bind self-managed calls to InCallService that support these kind of
calls.

Bug: 161144815
Test: TelecomUnitTest, manually test with test apps.
Change-Id: Ifa3566d5e681b5ab41fb3123a25fc841fa96fe60
Merged-In: Ifa3566d5e681b5ab41fb3123a25fc841fa96fe60
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index dc862ab..c5f3bde 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -44,6 +44,7 @@
 import android.telecom.ConnectionService;
 import android.telecom.DisconnectCause;
 import android.telecom.GatewayInfo;
+import android.telecom.InCallService;
 import android.telecom.Log;
 import android.telecom.Logging.EventManager;
 import android.telecom.ParcelableConference;
@@ -58,6 +59,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -510,6 +512,15 @@
     private boolean mIsSelfManaged = false;
 
     /**
+     * Indicates whether the {@link PhoneAccount} associated with an self-managed call want to
+     * expose the call to an {@link android.telecom.InCallService} which declares the metadata
+     * {@link TelecomManager#METADATA_INCLUDE_SELF_MANAGED_CALLS},
+     * For calls that {@link #mIsSelfManaged} is {@code false}, this value should be {@code false}
+     * as well.
+     */
+    private boolean mVisibleToInCallService = false;
+
+    /**
      * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
      * {@code True} if the phone account supports video calling, {@code false} otherwise.
      */
@@ -1628,6 +1639,14 @@
         setConnectionProperties(getConnectionProperties());
     }
 
+    public boolean visibleToInCallService() {
+        return mVisibleToInCallService;
+    }
+
+    public void setVisibleToInCallService(boolean visibleToInCallService) {
+        mVisibleToInCallService = visibleToInCallService;
+    }
+
     public void markFinishedHandoverStateAndCleanup(int handoverState) {
         if (mHandoverSourceCall != null) {
             mHandoverSourceCall.setHandoverState(handoverState);
@@ -3945,4 +3964,17 @@
     public void setCallScreeningComponentName(String callScreeningComponentName) {
         mCallScreeningComponentName = callScreeningComponentName;
     }
+
+    public void maybeOnInCallServiceTrackingChanged(boolean isTracking, boolean hasUi) {
+        if (mConnectionService == null) {
+            Log.w(this, "maybeOnInCallServiceTrackingChanged() request on a call"
+                    + " without a connection service.");
+        } else {
+            if (hasUi) {
+                mConnectionService.onUsingAlternativeUi(this, isTracking);
+            } else if (isTracking) {
+                mConnectionService.onTrackedByNonUiService(this, isTracking);
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 1e963c4..b6626a0 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -1237,10 +1237,14 @@
         PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
                 phoneAccountHandle);
         if (phoneAccount != null) {
+            Bundle phoneAccountExtras = phoneAccount.getExtras();
             call.setIsSelfManaged(phoneAccount.isSelfManaged());
             if (call.isSelfManaged()) {
                 // Self managed calls will always be voip audio mode.
                 call.setIsVoipAudioMode(true);
+                call.setVisibleToInCallService(phoneAccountExtras != null
+                        && phoneAccountExtras.getBoolean(
+                        PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false));
             } else {
                 // Incoming call is managed, the active call is self-managed and can't be held.
                 // We need to set extras on it to indicate whether answering will cause a 
@@ -1259,7 +1263,6 @@
                 }
             }
 
-            Bundle phoneAccountExtras = phoneAccount.getExtras();
             if (phoneAccountExtras != null
                     && phoneAccountExtras.getBoolean(
                             PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
@@ -1474,6 +1477,7 @@
 
         PhoneAccount account =
                 mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
+        Bundle phoneAccountExtra = account != null ? account.getExtras() : null;
         boolean isSelfManaged = account != null && account.isSelfManaged();
 
         // Create a call with original handle. The handle may be changed when the call is attached
@@ -1504,6 +1508,9 @@
             if (isSelfManaged) {
                 // Self-managed calls will ALWAYS use voip audio mode.
                 call.setIsVoipAudioMode(true);
+                call.setVisibleToInCallService(phoneAccountExtra != null
+                        && phoneAccountExtra.getBoolean(
+                                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, false));
             }
             call.setInitiatingUser(initiatingUser);
             isReusedCall = false;
@@ -4740,6 +4747,9 @@
         extras.putLong(TelecomManager.EXTRA_CALL_TELECOM_ROUTING_START_TIME_MILLIS,
               SystemClock.elapsedRealtime());
 
+        if (call.visibleToInCallService()) {
+            extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
+        }
         call.setIntentExtras(extras);
     }
 
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 72c3f24..1cb3957 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -1573,6 +1573,34 @@
         }
     }
 
+    /** @see IConnectionService#onUsingAlternativeUi(String, boolean, Session.Info) */
+    @VisibleForTesting
+    public void onUsingAlternativeUi(Call activeCall, boolean isUsingAlternativeUi) {
+        final String callId = mCallIdMapper.getCallId(activeCall);
+        if (callId != null && isServiceValid("onUsingAlternativeUi")) {
+            try {
+                logOutgoing("onUsingAlternativeUi %s", isUsingAlternativeUi);
+                mServiceInterface.onUsingAlternativeUi(callId, isUsingAlternativeUi,
+                        Log.getExternalSession(TELECOM_ABBREVIATION));
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /** @see IConnectionService#onTrackedByNonUiService(String, boolean, Session.Info) */
+    @VisibleForTesting
+    public void onTrackedByNonUiService(Call activeCall, boolean isTracked) {
+        final String callId = mCallIdMapper.getCallId(activeCall);
+        if (callId != null && isServiceValid("onTrackedByNonUiService")) {
+            try {
+                logOutgoing("onTrackedByNonUiService %s", isTracked);
+                mServiceInterface.onTrackedByNonUiService(callId, isTracked,
+                        Log.getExternalSession(TELECOM_ABBREVIATION));
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /** @see IConnectionService#disconnect(String, Session.Info) */
     void disconnect(Call call) {
         final String callId = mCallIdMapper.getCallId(call);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 53ad45b..e46d377 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -113,7 +113,7 @@
         public Call mCall;
     }
 
-    private class InCallServiceInfo {
+    public static class InCallServiceInfo {
         private final ComponentName mComponentName;
         private boolean mIsExternalCallsSupported;
         private boolean mIsSelfManagedCallsSupported;
@@ -279,7 +279,8 @@
             }
 
             if (call != null && call.isSelfManaged() &&
-                    !mInCallServiceInfo.isSelfManagedCallsSupported()) {
+                    (!mInCallServiceInfo.isSelfManagedCallsSupported()
+                            || !call.visibleToInCallService())) {
                 Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
                         mInCallServiceInfo);
                 mIsConnected = false;
@@ -342,6 +343,7 @@
                             mInCallServiceInfo.getType(),
                             mInCallServiceInfo.getDisconnectTime()
                                     - mInCallServiceInfo.getBindingStartTime(), mIsNullBinding);
+                    updateCallTracking(mCall, mInCallServiceInfo, false /* isAdd */);
                 }
 
                 InCallController.this.onDisconnected(mInCallServiceInfo);
@@ -748,6 +750,10 @@
                 newConnection.connect(callToConnectWith);
             }
         }
+
+        public List<InCallServiceBindingConnection> getSubConnections() {
+            return mSubConnections;
+        }
     }
 
     private final Call.Listener mCallListener = new Call.ListenerBase() {
@@ -915,12 +921,12 @@
         @Override
         public void onPackageUninstalled(String packageName) {
             mCarModeTracker.forceExitCarMode(packageName);
-            updateCarModeForSwitchingConnection();
+            updateCarModeForConnections();
         }
     };
 
     private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
-    private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
+    private static final int IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI = 1;
     private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
     private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
     private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
@@ -967,10 +973,9 @@
     private boolean mIsCallUsingMicrophone = false;
 
     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
-            SystemStateHelper systemStateHelper,
-            DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
-            EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker,
-            ClockProxy clockProxy) {
+            SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
+            Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
+            CarModeTracker carModeTracker, ClockProxy clockProxy) {
         mContext = context;
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
         mLock = lock;
@@ -1013,12 +1018,16 @@
                     continue;
                 }
 
-                if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+                if (call.isSelfManaged() && (!call.visibleToInCallService()
+                        || !info.isSelfManagedCallsSupported())) {
                     continue;
                 }
 
                 // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                boolean includeRttCall = false;
+                if (mInCallServiceConnection != null) {
+                    includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                }
 
                 componentsUpdated.add(info.getComponentName());
                 IInCallService inCallService = entry.getValue();
@@ -1030,6 +1039,7 @@
                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
                 try {
                     inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
+                    updateCallTracking(call, info, true /* isAdd */);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -1079,7 +1089,8 @@
                     continue;
                 }
 
-                if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+                if (call.isSelfManaged() && !call.visibleToInCallService()
+                        && !info.isSelfManagedCallsSupported()) {
                     continue;
                 }
 
@@ -1096,6 +1107,7 @@
                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
                 try {
                     inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
+                    updateCallTracking(call, info, true /* isAdd */);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -1356,7 +1368,8 @@
 
     /**
      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
-     * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
+     * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()}
+     * before invoking.
      *
      * @param call The newly added call that triggered the binding to the in-call services.
      */
@@ -1409,7 +1422,7 @@
         }
     }
 
-    private void connectToNonUiInCallServices(Call call) {
+    private void updateNonUiInCallServices() {
         List<InCallServiceInfo> nonUIInCallComponents =
                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
@@ -1419,7 +1432,7 @@
         List<String> callCompanionApps = mCallsManager
                 .getRoleManagerAdapter().getCallCompanionApps();
         if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
-            for(String pkg : callCompanionApps) {
+            for (String pkg : callCompanionApps) {
                 InCallServiceInfo info = getInCallServiceComponent(pkg,
                         IN_CALL_SERVICE_TYPE_COMPANION);
                 if (info != null) {
@@ -1427,7 +1440,14 @@
                 }
             }
         }
-        mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
+        mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(
+                nonUIInCalls);
+    }
+
+    private void connectToNonUiInCallServices(Call call) {
+        if (mNonUIInCallServiceConnections == null) {
+            updateNonUiInCallServices();
+        }
         mNonUIInCallServiceConnections.connect(call);
 
         IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
@@ -1444,7 +1464,7 @@
         InCallServiceInfo defaultDialerComponent =
                 (systemPackageName != null && systemPackageName.equals(packageName))
                 ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI)
-                : getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
+                : getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI);
         /* 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.
@@ -1494,7 +1514,6 @@
 
     private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
             ComponentName componentName, int requestedType) {
-
         List<InCallServiceInfo> retval = new LinkedList<>();
 
         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -1526,13 +1545,19 @@
                 if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
                     mKnownNonUiInCallServices.add(foundComponentName);
                 }
-                if (serviceInfo.enabled && (requestedType == 0 || requestedType == currentType)) {
-                    retval.add(new InCallServiceInfo(foundComponentName,
-                            isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
+
+                boolean isRequestedType;
+                if (requestedType == IN_CALL_SERVICE_TYPE_INVALID) {
+                    isRequestedType = true;
+                } else {
+                    isRequestedType = requestedType == currentType;
+                }
+                if (serviceInfo.enabled && isRequestedType) {
+                    retval.add(new InCallServiceInfo(foundComponentName, isExternalCallsSupported,
+                            isSelfManageCallsSupported, requestedType));
                 }
             }
         }
-
         return retval;
     }
 
@@ -1598,7 +1623,7 @@
                 mDefaultDialerCache.getDefaultDialerApplication(
                     mCallsManager.getCurrentUserHandle().getIdentifier()));
         if (isDefaultDialerPackage && isUIService) {
-            return IN_CALL_SERVICE_TYPE_DIALER_UI;
+            return IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
         }
 
         // Also allow any in-call service that has the control-experience permission (to ensure
@@ -1637,7 +1662,7 @@
 
         if (info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                 || info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
-                || info.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
+                || info.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI) {
             trackCallingUserInterfaceStarted(info);
         }
         IInCallService inCallService = IInCallService.Stub.asInterface(service);
@@ -1663,13 +1688,17 @@
         int numCallsSent = 0;
         for (Call call : calls) {
             try {
-                if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) ||
+                if ((call.isSelfManaged() && (!info.isSelfManagedCallsSupported()
+                        || !call.visibleToInCallService())) ||
                         (call.isExternalCall() && !info.isExternalCallsSupported())) {
                     continue;
                 }
 
                 // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                boolean includeRttCall = false;
+                if (mInCallServiceConnection != null) {
+                    includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                }
 
                 // Track the call if we don't already know about it.
                 addCall(call);
@@ -1683,6 +1712,7 @@
                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
                 inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
+                updateCallTracking(call, info, true /* isAdd */);
             } catch (RemoteException ignored) {
             }
         }
@@ -1709,7 +1739,7 @@
         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
         if (disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                 || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
-                || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
+                || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI) {
             trackCallingUserInterfaceStopped(disconnectedInfo);
         }
         mInCallServices.remove(disconnectedInfo);
@@ -1743,7 +1773,8 @@
                     continue;
                 }
 
-                if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+                if (call.isSelfManaged() && (!call.visibleToInCallService()
+                        || !info.isSelfManagedCallsSupported())) {
                     continue;
                 }
 
@@ -1824,7 +1855,7 @@
      */
     private ComponentName getConnectedUi() {
         InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
-                i -> i.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI
+                i -> i.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI
                         || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
                 .findAny()
                 .orElse(null);
@@ -1942,13 +1973,13 @@
             mCarModeTracker.handleExitCarMode(priority, packageName);
         }
 
-        updateCarModeForSwitchingConnection();
+        updateCarModeForConnections();
     }
 
-    public void updateCarModeForSwitchingConnection() {
+    public void updateCarModeForConnections() {
+        Log.i(this, "updateCarModeForConnections: car mode apps: %s",
+                mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
         if (mInCallServiceConnection != null) {
-            Log.i(this, "updateCarModeForSwitchingConnection: car mode apps: %s",
-                    mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
             if (shouldUseCarModeUI()) {
                 mInCallServiceConnection.changeCarModeApp(
                         mCarModeTracker.getCurrentCarModePackage());
@@ -2058,4 +2089,11 @@
         notificationManager.notify(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
                 builder.build());
     }
+
+    private void updateCallTracking(Call call, InCallServiceInfo info, boolean isAdd) {
+        int type = info.getType();
+        boolean hasUi = type == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
+                || type == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
+        call.maybeOnInCallServiceTrackingChanged(isAdd, hasUi);
+    }
 }
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 9c461ca..891a7a7 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -70,6 +70,8 @@
              android:exported="true">
             <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
                  android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                 android:value="true" />
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
diff --git a/testapps/carmodedialer/AndroidManifest.xml b/testapps/carmodedialer/AndroidManifest.xml
index f237211..239726c 100644
--- a/testapps/carmodedialer/AndroidManifest.xml
+++ b/testapps/carmodedialer/AndroidManifest.xml
@@ -37,8 +37,10 @@
         <service android:name="com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl"
              android:permission="android.permission.BIND_INCALL_SERVICE"
              android:exported="true">
-          <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
-               android:value="true"/>
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+                       android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                       android:value="true"/>
           <intent-filter>
               <action android:name="android.telecom.InCallService"/>
           </intent-filter>
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index 28f4473..d26d629 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -90,7 +90,6 @@
     <LinearLayout android:orientation="horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
-
         <Button
             android:id="@+id/placeOutgoingCallButton"
             android:layout_width="wrap_content"
@@ -113,6 +112,35 @@
             android:text="Req CallScreen Role"/>
     </LinearLayout>
 
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <Button
+            android:id="@+id/placeSelfManagedOutgoingCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="SelfManagedOutgoing"/>
+        <Button
+            android:id="@+id/placeSelfManagedIncomingCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="SelfManagedIncoming"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <Button
+            android:id="@+id/enableCarMode"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Enable car mode"/>
+        <Button
+            android:id="@+id/disableCarMode"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Disable car mode"/>
+    </LinearLayout>
     <ListView
         android:id="@+id/callList"
         android:layout_width="match_parent"
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 4b5fa57..d4661ff 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -46,8 +46,10 @@
 
     public static String SELF_MANAGED_ACCOUNT_1 = "1";
     public static String SELF_MANAGED_ACCOUNT_2 = "2";
+    public static String SELF_MANAGED_ACCOUNT_3 = "3";
     public static String SELF_MANAGED_NAME_1 = "SuperCall";
     public static String SELF_MANAGED_NAME_2 = "Mega Call";
+    public static String SELF_MANAGED_NAME_3 = "SM Call";
     public static String CUSTOM_URI_SCHEME = "custom";
 
     private static SelfManagedCallList sInstance;
@@ -99,6 +101,8 @@
                 SELF_MANAGED_NAME_1, true /* areCallsLogged */);
         registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
                 SELF_MANAGED_NAME_2, false /* areCallsLogged */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_3, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_3, true /* areCallsLogged */);
     }
 
     public void registerPhoneAccount(Context context, String id, Uri address, String name,
@@ -110,6 +114,9 @@
         if (areCallsLogged) {
             extras.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true);
         }
+        if (id.equals(SELF_MANAGED_ACCOUNT_3)) {
+            extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
+        }
         PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index fd12a2e..44410d2 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -16,9 +16,12 @@
 
 package com.android.server.telecom.testapps;
 
+import static android.app.UiModeManager.DEFAULT_PRIORITY;
+
 import android.app.Activity;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.UiModeManager;
 import android.app.role.RoleManager;
 import android.content.Intent;
 import android.media.AudioAttributes;
@@ -54,9 +57,13 @@
     private SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
     private CheckBox mCheckIfPermittedBeforeCalling;
     private Button mPlaceOutgoingCallButton;
+    private Button mPlaceSelfManagedOutgoingCallButton;
+    private Button mPlaceSelfManagedIncomingCallButton;
     private Button mPlaceIncomingCallButton;
     private Button mHandoverFrom;
     private Button mRequestCallScreeningRole;
+    private Button mEnableCarMode;
+    private Button mDisableCarMode;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
     private CheckBox mHoldableCheckbox;
@@ -119,6 +126,20 @@
                 placeOutgoingCall();
             }
         });
+        mPlaceSelfManagedOutgoingCallButton = (Button) findViewById(
+                R.id.placeSelfManagedOutgoingCallButton);
+        mPlaceSelfManagedOutgoingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                placeSelfManagedOutgoingCall();
+            }
+        });
+        mPlaceSelfManagedIncomingCallButton = (Button) findViewById(
+                R.id.placeSelfManagedIncomingCallButton);
+        mPlaceSelfManagedIncomingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) { placeSelfManagedIncomingCall(); }
+        });
         mPlaceIncomingCallButton = (Button) findViewById(R.id.placeIncomingCallButton);
         mPlaceIncomingCallButton.setOnClickListener(new View.OnClickListener() {
             @Override
@@ -134,7 +155,14 @@
         mRequestCallScreeningRole.setOnClickListener((v -> {
             requestCallScreeningRole();
         }));
-
+        mEnableCarMode = (Button) findViewById(R.id.enableCarMode);
+        mEnableCarMode.setOnClickListener((v -> {
+            enableCarMode();
+        }));
+        mDisableCarMode = (Button) findViewById(R.id.disableCarMode);
+        mDisableCarMode.setOnClickListener((v -> {
+            disableCarMode();
+        }));
         mUseAcct1Button = findViewById(R.id.useAcct1Button);
         mUseAcct2Button = findViewById(R.id.useAcct2Button);
         mHasFocus = findViewById(R.id.hasFocus);
@@ -184,6 +212,25 @@
         tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
     }
 
+    private void placeSelfManagedOutgoingCall() {
+        TelecomManager tm = TelecomManager.from(this);
+        PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
+                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+
+        if (mCheckIfPermittedBeforeCalling.isChecked()) {
+            Toast.makeText(this, R.string.outgoingCallNotPermitted, Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+        if (mVideoCallCheckbox.isChecked()) {
+            extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                    VideoProfile.STATE_BIDIRECTIONAL);
+        }
+        tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
+    }
+
     private void initiateHandover() {
         TelecomManager tm = TelecomManager.from(this);
         PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
@@ -214,6 +261,37 @@
         tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
     }
 
+    private void placeSelfManagedIncomingCall() {
+        TelecomManager tm = TelecomManager.from(this);
+        PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
+                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+
+        if (mCheckIfPermittedBeforeCalling.isChecked()) {
+            if (!tm.isIncomingCallPermitted(phoneAccountHandle)) {
+                Toast.makeText(this, R.string.incomingCallNotPermitted , Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                Uri.parse(mNumber.getText().toString()));
+        tm.addNewIncomingCall(phoneAccountHandle, extras);
+    }
+
+    private void enableCarMode() {
+        UiModeManager uiModeManager = getSystemService(UiModeManager.class);
+        uiModeManager.enableCarMode(0);
+        Toast.makeText(this, "Enabling car mode with priority " + DEFAULT_PRIORITY,
+                Toast.LENGTH_LONG).show();
+    }
+
+    private void disableCarMode() {
+        UiModeManager uiModeManager = getSystemService(UiModeManager.class);
+        uiModeManager.disableCarMode(0);
+        Toast.makeText(this, "Disabling car mode", Toast.LENGTH_LONG).show();
+    }
+
     private void configureNotificationChannel() {
         NotificationChannel channel = new NotificationChannel(
                 SelfManagedConnection.INCOMING_CALL_CHANNEL_ID, "Incoming Calls",
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 541d278..d326a29 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -19,13 +19,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 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;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.ComponentName;
 import android.net.Uri;
@@ -34,6 +38,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
 import android.widget.Toast;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -44,6 +49,8 @@
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.InCallController;
+import com.android.server.telecom.InCallController.InCallServiceInfo;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
@@ -54,17 +61,23 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
 public class CallTest extends TelecomTestCase {
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
+    private static final ComponentName COMPONENT_NAME_1 = ComponentName
+            .unflattenFromString("com.foo/.Blah");
+    private static final ComponentName COMPONENT_NAME_2 = ComponentName
+            .unflattenFromString("com.bar/.Blah");
     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
-            ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
+            COMPONENT_NAME_1, "Sim1");
     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
                     | PhoneAccount.CAPABILITY_CALL_PROVIDER)
             .setIsEnabled(true)
             .build();
+    private static final long TIMEOUT_MILLIS = 1000;
 
     @Mock private CallsManager mMockCallsManager;
     @Mock private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 26f24ef..5b4e800 100755
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -342,6 +342,14 @@
                 throws RemoteException { }
 
         @Override
+        public void onUsingAlternativeUi(String activeCallId, boolean usingAlternativeUi,
+                Session.Info info) throws RemoteException { }
+
+        @Override
+        public void onTrackedByNonUiService(String activeCallId, boolean isTracked,
+                Session.Info info) throws RemoteException { }
+
+        @Override
         public void playDtmfTone(String callId, char digit,
                 Session.Info info) throws RemoteException { }
 
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index df11198..0b926fe 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -1190,6 +1190,7 @@
                 anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(isExternalCall);
         when(mMockCall.isSelfManaged()).thenReturn(isSelfManagedCall);
+        when(mMockCall.visibleToInCallService()).thenReturn(isSelfManagedCall);
     }
 
     private ResolveInfo getDefResolveInfo(final boolean includeExternalCalls,