DO NOT MERGE
Fix security vulnerability issue for multi user call redirections.

Currently we won't check if the PhoneAccountHandle provided by a
CallRedirectionService has multi-user capability or belong to the same
user as the current user. Add the check and disconnect the call if this
is an unexpected cross-user call redirection.

Bug: 235098883
Test: CallsManagerTest, manual test with test app provided in
b/235098883.

Change-Id: Ia8b9468aa2bb8e3157c227e2617ff6a52e0af119
(cherry picked from commit f29ab7e1ec0e480e2d39d289d5aa3fc95aed2142)
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
old mode 100644
new mode 100755
index 0adfc41..4a3275f
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -1722,8 +1722,17 @@
 
         boolean endEarly = false;
         String disconnectReason = "";
-
         String callRedirectionApp = mRoleManagerAdapter.getDefaultCallRedirectionApp();
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar
+                .getPhoneAccountUnchecked(phoneAccountHandle);
+        if (phoneAccount != null
+                && !phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+            // Check if the phoneAccountHandle belongs to the current user
+            if (phoneAccountHandle != null &&
+                    !phoneAccountHandle.getUserHandle().equals(mCurrentUserHandle)) {
+                phoneAccountHandle = null;
+            }
+        }
 
         if (shouldCancelCall) {
             Log.w(this, "onCallRedirectionComplete: call is canceled");
@@ -1746,9 +1755,9 @@
             endEarly = true;
             disconnectReason = "Null handle from Call Redirection Service";
         } else if (phoneAccountHandle == null) {
-            Log.w(this, "onCallRedirectionComplete: phoneAccountHandle is null");
+            Log.w(this, "onCallRedirectionComplete: phoneAccountHandle is unavailable");
             endEarly = true;
-            disconnectReason = "Null phoneAccountHandle from Call Redirection Service";
+            disconnectReason = "Unavailable phoneAccountHandle from Call Redirection Service";
         } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext,
                 handle.getSchemeSpecificPart())) {
             Log.w(this, "onCallRedirectionComplete: emergency number %s is redirected from Call"
@@ -1770,6 +1779,7 @@
             return;
         }
 
+        final PhoneAccountHandle finalPhoneAccountHandle = phoneAccountHandle;
         if (uiAction.equals(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM)) {
             Log.addEvent(call, LogUtils.Events.REDIRECTION_USER_CONFIRMATION);
             mPendingRedirectedOutgoingCall = call;
@@ -1779,7 +1789,7 @@
                         @Override
                         public void loggedRun() {
                             Log.addEvent(call, LogUtils.Events.REDIRECTION_USER_CONFIRMED);
-                            call.setTargetPhoneAccount(phoneAccountHandle);
+                            call.setTargetPhoneAccount(finalPhoneAccountHandle);
                             placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn,
                                     videoState);
                         }
@@ -1789,7 +1799,7 @@
                     new Runnable("CM.oCRC", mLock) {
                         @Override
                         public void loggedRun() {
-                            call.setTargetPhoneAccount(phoneAccountHandle);
+                            call.setTargetPhoneAccount(finalPhoneAccountHandle);
                             placeOutgoingCall(call, handle, null, speakerphoneOn,
                                     videoState);
                         }
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index df19ce8..7adc99a 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -31,6 +31,9 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -39,8 +42,11 @@
 import android.os.Bundle;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.telecom.Connection;
 import android.telecom.Log;
+import android.telecom.DisconnectCause;
+import android.telecom.GatewayInfo;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -108,8 +114,12 @@
 @RunWith(JUnit4.class)
 public class CallsManagerTest extends TelecomTestCase {
     private static final int TEST_TIMEOUT = 5000;  // milliseconds
+    private static final int SECONDARY_USER_ID = 12;
     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
+    private static final PhoneAccountHandle SIM_1_HANDLE_SECONDARY = new PhoneAccountHandle(
+            ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1",
+            new UserHandle(SECONDARY_USER_ID));
     private static final PhoneAccountHandle SIM_2_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.foo/.Blah"), "Sim2");
     private static final PhoneAccountHandle VOIP_1_HANDLE = new PhoneAccountHandle(
@@ -525,13 +535,13 @@
     @Test
     public void testUnholdCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has different ConnectionService
-        Call heldCall = addSpyCall(VOIP_1_HANDLE);
+        Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
 
         // WHEN unhold the held call
         mCallsManager.unholdCall(heldCall);
@@ -549,14 +559,14 @@
     public void testUnholdCallWhenOngoingEmergCallCanNotBeHeldAndFromDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held, but it also an
         // emergency call.
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(true).when(ongoingCall).isEmergencyCall();
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has different ConnectionService
-        Call heldCall = addSpyCall(VOIP_1_HANDLE);
+        Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
 
         // WHEN unhold the held call
         mCallsManager.unholdCall(heldCall);
@@ -572,13 +582,13 @@
     @Test
     public void testUnholdCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a held call which has the same ConnectionService
-        Call heldCall = addSpyCall(SIM_2_HANDLE);
+        Call heldCall = addSpyCall(SIM_2_HANDLE, CallState.ON_HOLD);
 
         // WHEN unhold the held call
         mCallsManager.unholdCall(heldCall);
@@ -616,12 +626,12 @@
     @Test
     public void testAnswerCallWhenOngoingHasSameConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
+        Call incomingCall = addSpyCall(VOIP_1_HANDLE, CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN nothing happened on the ongoing call and the focus request for incoming call is sent
@@ -635,13 +645,13 @@
     @Test
     public void testAnswerCallWhenOngoingHasDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
+        Call incomingCall = addSpyCall(VOIP_1_HANDLE, CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is disconnected and the focus request for incoming call is sent
@@ -656,14 +666,14 @@
     @Test
     public void testAnswerCallWhenOngoingHasDifferentConnectionServiceButIsEmerg() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(true).when(ongoingCall).isEmergencyCall();
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // WHEN answer an incoming call
-        Call incomingCall = addSpyCall(VOIP_1_HANDLE);
+        Call incomingCall = addSpyCall(VOIP_1_HANDLE, CallState.RINGING);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is not disconnected
@@ -679,21 +689,21 @@
     public void testAnswerCallWhenMultipleHeldCallsExisted() {
         // Given an ongoing call and held call with the ConnectionService connSvr1. The
         // ConnectionService connSvr1 can handle one held call
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(CallState.ACTIVE).when(ongoingCall).getState();
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
-        Call heldCall = addSpyCall(SIM_1_HANDLE);
+        Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
         doReturn(CallState.ON_HOLD).when(heldCall).getState();
 
         // and other held call has difference ConnectionService
-        Call heldCall2 = addSpyCall(VOIP_1_HANDLE);
+        Call heldCall2 = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
         doReturn(CallState.ON_HOLD).when(heldCall2).getState();
 
         // WHEN answer an incoming call which ConnectionService is connSvr1
-        Call incomingCall = addSpyCall(SIM_1_HANDLE);
+        Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.RINGING);
         doReturn(true).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
@@ -752,13 +762,13 @@
     @Test
     public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(ongoingCall).when(mConnectionSvrFocusMgr).getCurrentFocusCall();
 
         // and a new self-managed call which has different ConnectionService
-        Call newCall = addSpyCall(VOIP_1_HANDLE);
+        Call newCall = addSpyCall(VOIP_1_HANDLE, CallState.ACTIVE);
         doReturn(true).when(newCall).isSelfManaged();
 
         // WHEN active the new call
@@ -776,13 +786,13 @@
     @Test
     public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndHasSameConnectionService() {
         // GIVEN a CallsManager with ongoing call, and this call can not be held
-        Call ongoingCall = addSpyCall(SIM_1_HANDLE);
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
 
         // and a new self-managed call which has the same ConnectionService
-        Call newCall = addSpyCall(SIM_1_HANDLE);
+        Call newCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
         doReturn(true).when(newCall).isSelfManaged();
 
         // WHEN active the new call
@@ -824,7 +834,7 @@
     @Test
     public void testDisconnectDialingCallOnIncoming() {
         // GIVEN a CallsManager with a self-managed call which is dialing, and this call can be held
-        Call ongoingCall = addSpyCall(SELF_MANAGED_HANDLE);
+        Call ongoingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.DIALING);
         ongoingCall.setState(CallState.DIALING, "test");
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
@@ -857,7 +867,7 @@
     @Test
     public void testNoFilteringOfSelfManagedCalls() {
         // GIVEN an incoming call which is self managed.
-        Call incomingCall = addSpyCall(SELF_MANAGED_HANDLE);
+        Call incomingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.NEW);
         doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         doReturn(true).when(incomingCall).isSelfManaged();
@@ -974,7 +984,7 @@
     @Test
     public void testNoFilteringOfCallsWhenPhoneAccountRequestsSkipped() {
         // GIVEN an incoming call which is from a PhoneAccount that requested to skip filtering.
-        Call incomingCall = addSpyCall(SIM_1_HANDLE);
+        Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
         Bundle extras = new Bundle();
         extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
         PhoneAccount skipRequestedAccount = new PhoneAccount.Builder(SIM_2_HANDLE, "Skipper")
@@ -1084,11 +1094,61 @@
                 Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
     }
 
-    private Call addSpyCall() {
-        return addSpyCall(SIM_2_HANDLE);
+    @SmallTest
+    @Test
+    public void testCrossUserCallRedirectionEndEarlyForIncapablePhoneAccount() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(SIM_1_HANDLE_SECONDARY)))
+                .thenReturn(SIM_1_ACCOUNT);
+        mCallsManager.onUserSwitch(UserHandle.SYSTEM);
+
+        Call callSpy = addSpyCall(CallState.NEW);
+        mCallsManager.onCallRedirectionComplete(callSpy, TEST_ADDRESS, SIM_1_HANDLE_SECONDARY,
+                new GatewayInfo("foo", TEST_ADDRESS2, TEST_ADDRESS), true /* speakerphoneOn */,
+                VideoProfile.STATE_AUDIO_ONLY, false /* shouldCancelCall */, "" /* uiAction */);
+
+        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
+        verify(callSpy).disconnect(argumentCaptor.capture());
+        assertTrue(argumentCaptor.getValue().contains("Unavailable phoneAccountHandle"));
     }
 
-    private Call addSpyCall(PhoneAccountHandle targetPhoneAccount) {
+    private Call addSpyCall() {
+        return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
+    }
+
+    private Call addSpyCall(int initialState) {
+        return addSpyCall(SIM_2_HANDLE, initialState);
+    }
+
+    private Call addSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
+        return addSpyCall(targetPhoneAccount, null, initialState, 0 /*caps*/, 0 /*props*/);
+    }
+
+    private Call addSpyCall(PhoneAccountHandle targetPhoneAccount,
+            PhoneAccountHandle connectionMgrAcct, int initialState) {
+        return addSpyCall(targetPhoneAccount, connectionMgrAcct, initialState, 0 /*caps*/,
+                0 /*props*/);
+    }
+
+    private Call addSpyCall(PhoneAccountHandle targetPhoneAccount,
+            PhoneAccountHandle connectionMgrAcct, int initialState,
+            int connectionCapabilities, int connectionProperties) {
+        Call ongoingCall = createCall(targetPhoneAccount, connectionMgrAcct, initialState);
+        ongoingCall.setConnectionProperties(connectionProperties);
+        ongoingCall.setConnectionCapabilities(connectionCapabilities);
+        Call callSpy = Mockito.spy(ongoingCall);
+
+        // Mocks some methods to not call the real method.
+        doNothing().when(callSpy).unhold();
+        doNothing().when(callSpy).hold();
+        doNothing().when(callSpy).answer(Matchers.anyInt());
+        doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
+
+        mCallsManager.addCall(callSpy);
+        return callSpy;
+    }
+
+    private Call createCall(PhoneAccountHandle targetPhoneAccount,
+            PhoneAccountHandle connectionManagerAccount, int initialState) {
         Call ongoingCall = new Call(String.format("TC@%d", sCallId++), /* callId */
                 mComponentContextFixture.getTestDouble(),
                 mCallsManager,
@@ -1097,24 +1157,18 @@
                 mPhoneNumberUtilsAdapter,
                 TEST_ADDRESS,
                 null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
+                connectionManagerAccount,
                 targetPhoneAccount,
                 Call.CALL_DIRECTION_INCOMING,
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
                 mClockProxy);
-        ongoingCall.setState(CallState.ACTIVE, "just cuz");
-        Call callSpy = Mockito.spy(ongoingCall);
+        ongoingCall.setState(initialState, "just cuz");
+        return ongoingCall;
+    }
 
-        // Mocks some methods to not call the real method.
-        doNothing().when(callSpy).unhold();
-        doNothing().when(callSpy).hold();
-        doNothing().when(callSpy).disconnect();
-        doNothing().when(callSpy).answer(Matchers.anyInt());
-        doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
-
-        mCallsManager.addCall(callSpy);
-        return callSpy;
+    private Call createCall(PhoneAccountHandle targetPhoneAccount, int initialState) {
+        return createCall(targetPhoneAccount, null /* connectionManager */, initialState);
     }
 
     private void verifyFocusRequestAndExecuteCallback(Call call) {