Resolve StatusHints image exploit across user.

Because of the INTERACT_ACROSS_USERS permission, an app that implements
a ConnectionService can upload an image icon belonging to another user
by setting it in the StatusHints. Validating the construction of the
StatusHints on the calling user would prevent a malicious app from
registering a connection service with the embedded image icon from a
different user.

From additional feedback, this CL also addresses potential
vulnerabilities in an app being able to directly invoke the binder for a
means to manipulate the contents of the bundle that are passed with it.
The targeted points of entry are in ConnectionServiceWrapper for the
following APIs: handleCreateConnectionComplete, setStatusHints,
addConferenceCall, and addExistingConnection.

Fixes: 280797684
Test: Manual (verified that original exploit is no longer an issue).
Test: Unit test for validating image in StatusHints constructor.
Test: Unit tests to address vulnerabilities via the binder.
Change-Id: Ie1f6a8866d31d5f1099dd0630cf8e9ee782d389c
Merged-In: Ie1f6a8866d31d5f1099dd0630cf8e9ee782d389c
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 5bb1dbe..d38af57 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
@@ -83,10 +84,17 @@
                 ParcelableConnection connection, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
                     mPackageAbbreviation);
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     logIncoming("handleCreateConnectionComplete %s", callId);
+                    // Check status hints image for cross user access
+                    if (connection.getStatusHints() != null) {
+                        Icon icon = connection.getStatusHints().getIcon();
+                        connection.getStatusHints().setIcon(StatusHints.
+                                validateAccountIconUserBoundary(icon, callingUserHandle));
+                    }
                     ConnectionServiceWrapper.this
                             .handleCreateConnectionComplete(callId, request, connection);
 
@@ -482,6 +490,14 @@
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
                     mPackageAbbreviation);
 
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
+            // Check status hints image for cross user access
+            if (parcelableConference.getStatusHints() != null) {
+                Icon icon = parcelableConference.getStatusHints().getIcon();
+                parcelableConference.getStatusHints().setIcon(StatusHints.
+                        validateAccountIconUserBoundary(icon, callingUserHandle));
+            }
+
             if (parcelableConference.getConnectElapsedTimeMillis() != 0
                     && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
                             != PackageManager.PERMISSION_GRANTED) {
@@ -727,10 +743,17 @@
         public void setStatusHints(String callId, StatusHints statusHints,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, "CSW.sSH", mPackageAbbreviation);
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     logIncoming("setStatusHints %s %s", callId, statusHints);
+                    // Check status hints image for cross user access
+                    if (statusHints != null) {
+                        Icon icon = statusHints.getIcon();
+                        statusHints.setIcon(StatusHints.validateAccountIconUserBoundary(
+                                icon, callingUserHandle));
+                    }
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         call.setStatusHints(statusHints);
@@ -962,6 +985,14 @@
                                     connection.getCallDirection(),
                                     connection.getCallerNumberVerificationStatus());
                         }
+
+                        // Check status hints image for cross user access
+                        if (connection.getStatusHints() != null) {
+                            Icon icon = connection.getStatusHints().getIcon();
+                            connection.getStatusHints().setIcon(StatusHints.
+                                    validateAccountIconUserBoundary(icon, userHandle));
+                        }
+
                         // Check to see if this Connection has already been added.
                         Call alreadyAddedConnection = mCallsManager
                                 .getAlreadyAddedConnection(connectIdToCheck);
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 049a501..7bce5a6 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -16,8 +16,11 @@
 
 package com.android.server.telecom.tests;
 
+import static com.android.server.telecom.tests.ConnectionServiceFixture.STATUS_HINTS_EXTRA;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -38,10 +41,14 @@
 import android.content.IContentProvider;
 import android.content.pm.PackageManager;
 import android.media.AudioDeviceInfo;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.BlockedNumberContract;
@@ -54,12 +61,14 @@
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telecom.IInCallAdapter;
 import android.telecom.CallerInfo;
@@ -194,7 +203,7 @@
     @Test
     public void testTelecomManagerAcceptRingingVideoCall() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -223,7 +232,7 @@
     @Test
     public void testTelecomManagerAcceptRingingVideoCallAsAudio() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -254,7 +263,7 @@
     @Test
     public void testTelecomManagerAcceptRingingInvalidVideoState() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -692,13 +701,13 @@
     @MediumTest
     @Test
     public void testBasicConferenceCall() throws Exception {
-        makeConferenceCall();
+        makeConferenceCall(null, null);
     }
 
     @MediumTest
     @Test
     public void testAddCallToConference1() throws Exception {
-        ParcelableCall conferenceCall = makeConferenceCall();
+        ParcelableCall conferenceCall = makeConferenceCall(null, null);
         IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
         // testAddCallToConference{1,2} differ in the order of arguments to InCallAdapter#conference
@@ -716,7 +725,7 @@
     @MediumTest
     @Test
     public void testAddCallToConference2() throws Exception {
-        ParcelableCall conferenceCall = makeConferenceCall();
+        ParcelableCall conferenceCall = makeConferenceCall(null, null);
         IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
         mInCallServiceFixtureX.getInCallAdapter()
@@ -974,7 +983,7 @@
     public void testOutgoingCallSelectPhoneAccountVideo() throws Exception {
         startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
                 null, mConnectionServiceFixtureA,
-                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
+                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
         assert(call.isVideoCallingSupportedByPhoneAccount());
@@ -997,7 +1006,7 @@
     public void testOutgoingCallSelectPhoneAccountNoVideo() throws Exception {
         startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
                 null, mConnectionServiceFixtureA,
-                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
+                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
         assert(call.isVideoCallingSupportedByPhoneAccount());
@@ -1212,4 +1221,145 @@
         assertTrue(muteValues.get(0));
         assertFalse(muteValues.get(1));
     }
+
+    /**
+     * Verifies that StatusHints image is validated in ConnectionServiceWrapper#addConferenceCall
+     * when the image doesn't belong to the calling user. Simulates a scenario where an app
+     * could manipulate the contents of the bundle and send it via the binder to upload an image
+     * from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_addConferenceCall() throws Exception {
+        Intent callIntent1 = new Intent();
+        // Stub intent for call2
+        Intent callIntent2 = new Intent();
+        Bundle callExtras1 = new Bundle();
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        // Load StatusHints extra into TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS to be processed
+        // as the call extras. This will be leveraged in ConnectionServiceFixture to set the
+        // StatusHints for the given connection.
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        callExtras1.putParcelable(STATUS_HINTS_EXTRA, statusHints);
+        callIntent1.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras1);
+
+        // Start conference call to invoke ConnectionServiceWrapper#addConferenceCall.
+        // Note that the calling user would be User 0.
+        ParcelableCall conferenceCall = makeConferenceCall(callIntent1, callIntent2);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(mInCallServiceFixtureX.mLatestCallId)
+                .getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(mInCallServiceFixtureX.mLatestCallId)
+                .getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in
+     * ConnectionServiceWrapper#handleCreateConnectionComplete when the image doesn't belong to the
+     * calling user. Simulates a scenario where an app could manipulate the contents of the
+     * bundle and send it via the binder to upload an image from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_handleCreateConnectionComplete() throws Exception {
+        Bundle extras = new Bundle();
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        // Load the bundle with the test extra in order to simulate an app directly invoking the
+        // binder on ConnectionServiceWrapper#handleCreateConnectionComplete.
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        extras.putParcelable(STATUS_HINTS_EXTRA, statusHints);
+
+        // Start incoming call with StatusHints extras
+        // Note that the calling user in ConnectionServiceWrapper#handleCreateConnectionComplete
+        // would be User 0.
+        IdPair ids = startIncomingPhoneCallWithExtras("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, extras);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(ids.mCallId).getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(ids.mCallId).getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in ConnectionServiceWrapper#setStatusHints
+     * when the image doesn't belong to the calling user. Simulates a scenario where an app
+     * could manipulate the contents of the bundle and send it via the binder to upload an image
+     * from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_setStatusHints() throws Exception {
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1214",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        // Modify existing connection with StatusHints image exploit
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
+                .mConnectionById.get(outgoing.mConnectionId);
+        connectionInfo.statusHints = statusHints;
+
+        // Invoke ConnectionServiceWrapper#setStatusHints.
+        // Note that the calling user would be User 0.
+        mConnectionServiceFixtureA.sendSetStatusHints(outgoing.mConnectionId);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(outgoing.mCallId).getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(outgoing.mCallId)
+                .getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in
+     * ConnectionServiceWrapper#addExistingConnection when the image doesn't belong to the calling
+     * user. Simulates a scenario where an app could manipulate the contents of the bundle and
+     * send it via the binder to upload an image from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_addExistingConnection() throws Exception {
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1214",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        Connection existingConnection = mConnectionServiceFixtureA.mLatestConnection;
+
+        // Modify existing connection with StatusHints image exploit
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        StatusHints modifiedStatusHints = new StatusHints(icon);
+        assertNotNull(modifiedStatusHints.getIcon());
+        ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
+                .mConnectionById.get(outgoing.mConnectionId);
+        connectionInfo.statusHints = modifiedStatusHints;
+
+        // Invoke ConnectionServiceWrapper#addExistingConnection.
+        // Note that the calling user would be User 0.
+        mConnectionServiceFixtureA.sendAddExistingConnection(outgoing.mConnectionId);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+
+        // Ensure that StatusHints was set. Due to test setup, the ParcelableConnection object that
+        // is passed into sendAddExistingConnection is instantiated on invocation. The call's
+        // StatusHints are not updated at the time of completion, so instead, we can verify that
+        // the ParcelableConnection object was modified.
+        assertNotNull(mConnectionServiceFixtureA.mLatestParcelableConnection.getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mConnectionServiceFixtureA.mLatestParcelableConnection
+                .getStatusHints().getIcon());
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
index 926d740..cf44cfe 100644
--- a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
@@ -370,7 +370,7 @@
     @LargeTest
     @Test
     public void testConferenceSetExtras() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
 
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
@@ -414,7 +414,7 @@
     @FlakyTest(bugId = 117751305)
     @Test
     public void testConferenceExtraOperations() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
         assertNotNull(conference);
@@ -450,7 +450,7 @@
     @LargeTest
     @Test
     public void testConferenceICS() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
 
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 6e6646f..9f0b6aa 100755
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -68,6 +68,7 @@
     static int INVALID_VIDEO_STATE = -1;
     public CountDownLatch mExtrasLock = new CountDownLatch(1);
     static int NOT_SPECIFIED = 0;
+    public static final String STATUS_HINTS_EXTRA = "updateStatusHints";
 
     /**
      * Implementation of ConnectionService that performs no-ops for tasks normally meant for
@@ -102,6 +103,11 @@
             if (mProperties != NOT_SPECIFIED) {
                 fakeConnection.setConnectionProperties(mProperties);
             }
+            // Testing for StatusHints image icon cross user access
+            if (request.getExtras() != null) {
+                fakeConnection.setStatusHints(
+                        request.getExtras().getParcelable(STATUS_HINTS_EXTRA));
+            }
 
             return fakeConnection;
         }
@@ -118,6 +124,11 @@
             if (mProperties != NOT_SPECIFIED) {
                 fakeConnection.setConnectionProperties(mProperties);
             }
+            // Testing for StatusHints image icon cross user access
+            if (request.getExtras() != null) {
+                fakeConnection.setStatusHints(
+                        request.getExtras().getParcelable(STATUS_HINTS_EXTRA));
+            }
             return fakeConnection;
         }
 
@@ -134,6 +145,12 @@
                 Conference fakeConference = new FakeConference();
                 fakeConference.addConnection(cxn1);
                 fakeConference.addConnection(cxn2);
+                if (cxn1.getStatusHints() != null || cxn2.getStatusHints() != null) {
+                    // For testing purposes, pick one of the status hints that isn't null.
+                    StatusHints statusHints = cxn1.getStatusHints() != null
+                            ? cxn1.getStatusHints() : cxn2.getStatusHints();
+                    fakeConference.setStatusHints(statusHints);
+                }
                 mLatestConference = fakeConference;
                 addConference(fakeConference);
             } else {
@@ -494,6 +511,7 @@
 
     public String mLatestConnectionId;
     public Connection mLatestConnection;
+    public ParcelableConnection mLatestParcelableConnection;
     public Conference mLatestConference;
     public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>();
     public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>();
@@ -738,7 +756,7 @@
     }
 
     private ParcelableConnection parcelable(ConnectionInfo c) {
-        return new ParcelableConnection(
+        mLatestParcelableConnection = new ParcelableConnection(
                 c.request.getAccountHandle(),
                 c.state,
                 c.capabilities,
@@ -759,5 +777,6 @@
                 c.conferenceableConnectionIds,
                 c.extras,
                 c.callerNumberVerificationStatus);
+        return mLatestParcelableConnection;
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index d6ff196..137cf8b 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -411,12 +411,13 @@
         super.tearDown();
     }
 
-    protected ParcelableCall makeConferenceCall() throws Exception {
-        IdPair callId1 = startAndMakeActiveOutgoingCall("650-555-1212",
-                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+    protected ParcelableCall makeConferenceCall(
+            Intent callIntentExtras1, Intent callIntentExtras2) throws Exception {
+        IdPair callId1 = startAndMakeActiveOutgoingCallWithExtras("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, callIntentExtras1);
 
-        IdPair callId2 = startAndMakeActiveOutgoingCall("650-555-1213",
-                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        IdPair callId2 = startAndMakeActiveOutgoingCallWithExtras("650-555-1213",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, callIntentExtras2);
 
         IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
         inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
@@ -618,7 +619,7 @@
 
         startOutgoingPhoneCallWaitForBroadcaster(number, null,
                 connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
-                false /*isEmergency*/);
+                false /*isEmergency*/, null);
 
         return mInCallServiceFixtureX.mLatestCallId;
     }
@@ -648,17 +649,17 @@
             throws Exception {
 
         return startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                initiatingUser, VideoProfile.STATE_AUDIO_ONLY);
+                initiatingUser, VideoProfile.STATE_AUDIO_ONLY, null);
     }
 
     protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState) throws Exception {
+            int videoState, Intent callIntentExtras) throws Exception {
         int startingNumConnections = connectionServiceFixture.mConnectionById.size();
         int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
 
         startOutgoingPhoneCallPendingCreateConnection(number, phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState);
+                connectionServiceFixture, initiatingUser, videoState, callIntentExtras);
 
         verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
                 .createConnectionComplete(anyString(), any());
@@ -701,7 +702,7 @@
 
         // Call will not use the ordered broadcaster, since it is an Emergency Call
         startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
+                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/, null);
 
         return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
                 phoneAccountHandle, connectionServiceFixture);
@@ -710,7 +711,7 @@
     protected void startOutgoingPhoneCallWaitForBroadcaster(String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState, boolean isEmergency) throws Exception {
+            int videoState, boolean isEmergency, Intent actionCallIntent) throws Exception {
         reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
@@ -723,7 +724,9 @@
 
         boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
 
-        Intent actionCallIntent = new Intent();
+        if (actionCallIntent == null) {
+            actionCallIntent = new Intent();
+        }
         actionCallIntent.setData(Uri.parse("tel:" + number));
         actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
         if(isEmergency) {
@@ -768,9 +771,10 @@
     protected String startOutgoingPhoneCallPendingCreateConnection(String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState) throws Exception {
+            int videoState, Intent callIntentExtras) throws Exception {
         startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
+                connectionServiceFixture, initiatingUser,
+                videoState, false /*isEmergency*/, callIntentExtras);
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
         verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
@@ -875,14 +879,24 @@
             PhoneAccountHandle phoneAccountHandle,
             final ConnectionServiceFixture connectionServiceFixture) throws Exception {
         return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
-                connectionServiceFixture);
+                connectionServiceFixture, null);
+    }
+
+    protected IdPair startIncomingPhoneCallWithExtras(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            final ConnectionServiceFixture connectionServiceFixture,
+            Bundle extras) throws Exception {
+        return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
+                connectionServiceFixture, extras);
     }
 
     protected IdPair startIncomingPhoneCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
             int videoState,
-            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
+            final ConnectionServiceFixture connectionServiceFixture,
+            Bundle extras) throws Exception {
         reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
@@ -899,7 +913,9 @@
                 new IncomingCallAddedListener(incomingCallAddedLatch);
         mTelecomSystem.getCallsManager().addListener(callAddedListener);
 
-        Bundle extras = new Bundle();
+        if (extras == null) {
+            extras = new Bundle();
+        }
         extras.putParcelable(
                 TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
@@ -990,7 +1006,16 @@
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
         return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
-                VideoProfile.STATE_AUDIO_ONLY);
+                VideoProfile.STATE_AUDIO_ONLY, null);
+    }
+
+    protected IdPair startAndMakeActiveOutgoingCallWithExtras(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture,
+            Intent callIntentExtras) throws Exception {
+        return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
+                VideoProfile.STATE_AUDIO_ONLY, callIntentExtras);
     }
 
     // A simple outgoing call, verifying that the appropriate connection service is contacted,
@@ -998,9 +1023,10 @@
     protected IdPair startAndMakeActiveOutgoingCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
-            ConnectionServiceFixture connectionServiceFixture, int videoState) throws Exception {
+            ConnectionServiceFixture connectionServiceFixture, int videoState,
+            Intent callIntentExtras) throws Exception {
         IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                Process.myUserHandle(), videoState);
+                Process.myUserHandle(), videoState, callIntentExtras);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
         if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
@@ -1109,7 +1135,7 @@
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
         IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY, null);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
         if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index 97e71d1..84beedc 100644
--- a/tests/src/com/android/server/telecom/tests/VideoCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -105,7 +105,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
     }
@@ -121,7 +121,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_TX_ENABLED);
+                VideoProfile.STATE_TX_ENABLED, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
     }
@@ -137,7 +137,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_AUDIO_ONLY);
+                VideoProfile.STATE_AUDIO_ONLY, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_EARPIECE);
     }
@@ -165,7 +165,7 @@
     @Test
     public void testIncomingVideoCallMissedCheckVideoHistory() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -182,7 +182,7 @@
     @Test
     public void testIncomingVideoCallRejectedCheckVideoHistory() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -201,7 +201,7 @@
     public void testOutgoingVideoCallCanceledCheckVideoHistory() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -219,7 +219,7 @@
     public void testOutgoingVideoCallRejectedCheckVideoHistory() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -237,7 +237,7 @@
     public void testOutgoingVideoCallAnsweredAsAudio() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();