Fix NPE in TelecomManager#isOutgoingCallAllowed

There is a potential for a NPE if "excludeCall" is null.  Adding null
check and unit tests to verify operation of the isOutgoingCallAllowed API,
as well as basic outgoing self-managed calls (which rely on this API).

Test: Added unit tests.
Change-Id: Ia5e92a7cb418af0d131daa2ca88b7cd4d36ab057
Fixes: 67495237
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 10c1fc3..7485841 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -2948,10 +2948,10 @@
             // Only permit outgoing calls if there is no ongoing emergency calls and all other calls
             // are associated with the current PhoneAccountHandle.
             return !hasEmergencyCall() && (
-                    excludeCall.getHandoverSourceCall() != null ||
-                            (!hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle) &&
-                            !hasCallsForOtherPhoneAccount(phoneAccountHandle) &&
-                            !hasManagedCalls()));
+                    (excludeCall != null && excludeCall.getHandoverSourceCall() != null) || (
+                            !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle)
+                                    && !hasCallsForOtherPhoneAccount(phoneAccountHandle)
+                                    && !hasManagedCalls()));
         }
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 32ac1ac..f391ee6 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -900,4 +900,59 @@
         assert(!call.isVideoCallingSupported());
         assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
     }
+
+    /**
+     * Basic test to ensure that a self-managed ConnectionService can place a call.
+     * @throws Exception
+     */
+    @LargeTest
+    public void testSelfManagedOutgoing() throws Exception {
+        PhoneAccountHandle phoneAccountHandle = mPhoneAccountSelfManaged.getAccountHandle();
+        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212", phoneAccountHandle,
+                mConnectionServiceFixtureA);
+
+        // The InCallService should not know about the call since its self-managed.
+        assertNull(mInCallServiceFixtureX.getCall(ids.mCallId));
+    }
+
+    /**
+     * Basic test to ensure that a self-managed ConnectionService can add an incoming call.
+     * @throws Exception
+     */
+    @LargeTest
+    public void testSelfManagedIncoming() throws Exception {
+        PhoneAccountHandle phoneAccountHandle = mPhoneAccountSelfManaged.getAccountHandle();
+        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212", phoneAccountHandle,
+                mConnectionServiceFixtureA);
+
+        // The InCallService should not know about the call since its self-managed.
+        assertNull(mInCallServiceFixtureX.getCall(ids.mCallId));
+    }
+
+    /**
+     * Basic test to ensure that when there are no calls, we permit outgoing calls by a self managed
+     * CS.
+     * @throws Exception
+     */
+    @LargeTest
+    public void testIsOutgoingCallPermitted() throws Exception {
+        assertTrue(mTelecomSystem.getTelecomServiceImpl().getBinder()
+                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+    }
+
+    /**
+     * Basic test to ensure that when there are other calls, we do not permit outgoing calls by a
+     * self managed CS.
+     * @throws Exception
+     */
+    @LargeTest
+    public void testIsOutgoingCallPermittedOngoing() throws Exception {
+        // Start a regular call; the self-managed CS can't make a call now.
+        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+
+        assertFalse(mTelecomSystem.getTelecomServiceImpl().getBinder()
+                .isOutgoingCallPermitted(mPhoneAccountSelfManaged.getAccountHandle()));
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 7f81f05..475b550 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -260,6 +260,16 @@
                             PhoneAccount.CAPABILITY_CALL_PROVIDER |
                                     PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
                     .build();
+    final PhoneAccount mPhoneAccountSelfManaged =
+            PhoneAccount.builder(
+                    new PhoneAccountHandle(
+                            mConnectionServiceComponentNameA,
+                            "id SM"),
+                    "Phone account service A SM")
+                    .addSupportedUriScheme("tel")
+                    .setCapabilities(
+                            PhoneAccount.CAPABILITY_SELF_MANAGED)
+                    .build();
     final PhoneAccount mPhoneAccountB0 =
             PhoneAccount.builder(
                     new PhoneAccountHandle(
@@ -468,6 +478,7 @@
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA1);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA2);
+        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountSelfManaged);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
@@ -663,7 +674,8 @@
         mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
                 CallerInfoAsyncQueryFactoryFixture.Request::reply);
 
-        if (!hasInCallAdapter) {
+        boolean isSelfManaged = phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle();
+        if (!hasInCallAdapter && !isSelfManaged) {
             verify(mInCallServiceFixtureX.getTestDouble())
                     .setInCallAdapter(
                             any(IInCallAdapter.class));
@@ -685,27 +697,28 @@
         ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
-        verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
-                times(mNumOutgoingCallsMade))
-                .sendOrderedBroadcastAsUser(
-                        newOutgoingCallIntent.capture(),
-                        any(UserHandle.class),
-                        anyString(),
-                        anyInt(),
-                        newOutgoingCallReceiver.capture(),
-                        nullable(Handler.class),
-                        anyInt(),
-                        anyString(),
-                        nullable(Bundle.class));
-
-        // Pass on the new outgoing call Intent
-        // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
-        newOutgoingCallReceiver.getValue().setPendingResult(
-                new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
-        newOutgoingCallReceiver.getValue().setResultData(
-                newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
-        newOutgoingCallReceiver.getValue().onReceive(mComponentContextFixture.getTestDouble(),
-                newOutgoingCallIntent.getValue());
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
+                    times(mNumOutgoingCallsMade))
+                    .sendOrderedBroadcastAsUser(
+                            newOutgoingCallIntent.capture(),
+                            any(UserHandle.class),
+                            anyString(),
+                            anyInt(),
+                            newOutgoingCallReceiver.capture(),
+                            nullable(Handler.class),
+                            anyInt(),
+                            anyString(),
+                            nullable(Bundle.class));
+            // Pass on the new outgoing call Intent
+            // Set a dummy PendingResult so the BroadcastReceiver agrees to accept onReceive()
+            newOutgoingCallReceiver.getValue().setPendingResult(
+                    new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
+            newOutgoingCallReceiver.getValue().setResultData(
+                    newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+            newOutgoingCallReceiver.getValue().onReceive(mComponentContextFixture.getTestDouble(),
+                    newOutgoingCallIntent.getValue());
+        }
 
         return mInCallServiceFixtureX.mLatestCallId;
     }
@@ -752,8 +765,13 @@
         verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
                 .createConnectionComplete(anyString(), any());
 
-        assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
-        assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
+        if (phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(startingNumCalls, mInCallServiceFixtureX.mCallById.size());
+            assertEquals(startingNumCalls, mInCallServiceFixtureY.mCallById.size());
+        } else {
+            assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
+            assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
+        }
 
         assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
 
@@ -829,58 +847,59 @@
         // is added, future interactions as triggered by the ConnectionService, through the various
         // test fixtures, will be synchronous.
 
-        if (!hasInCallAdapter) {
+        if (!hasInCallAdapter
+                && phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
             verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
                     .setInCallAdapter(any(IInCallAdapter.class));
             verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
                     .setInCallAdapter(any(IInCallAdapter.class));
+
+            // Give the InCallService time to respond
+            assertTrueWithTimeout(new Predicate<Void>() {
+                @Override
+                public boolean apply(Void v) {
+                    return mInCallServiceFixtureX.mInCallAdapter != null;
+                }
+            });
+
+            assertTrueWithTimeout(new Predicate<Void>() {
+                @Override
+                public boolean apply(Void v) {
+                    return mInCallServiceFixtureY.mInCallAdapter != null;
+                }
+            });
+
+            verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
+                    .addCall(any(ParcelableCall.class));
+            verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
+                    .addCall(any(ParcelableCall.class));
+
+            // Give the InCallService time to respond
+
+            assertTrueWithTimeout(new Predicate<Void>() {
+                @Override
+                public boolean apply(Void v) {
+                    return startingNumConnections + 1 ==
+                            connectionServiceFixture.mConnectionById.size();
+                }
+            });
+            assertTrueWithTimeout(new Predicate<Void>() {
+                @Override
+                public boolean apply(Void v) {
+                    return startingNumCalls + 1 == mInCallServiceFixtureX.mCallById.size();
+                }
+            });
+            assertTrueWithTimeout(new Predicate<Void>() {
+                @Override
+                public boolean apply(Void v) {
+                    return startingNumCalls + 1 == mInCallServiceFixtureY.mCallById.size();
+                }
+            });
+
+            assertEquals(mInCallServiceFixtureX.mLatestCallId,
+                    mInCallServiceFixtureY.mLatestCallId);
         }
 
-        // Give the InCallService time to respond
-
-        assertTrueWithTimeout(new Predicate<Void>() {
-            @Override
-            public boolean apply(Void v) {
-                return mInCallServiceFixtureX.mInCallAdapter != null;
-            }
-        });
-
-        assertTrueWithTimeout(new Predicate<Void>() {
-            @Override
-            public boolean apply(Void v) {
-                return mInCallServiceFixtureY.mInCallAdapter != null;
-            }
-        });
-
-        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
-                .addCall(any(ParcelableCall.class));
-        verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
-                .addCall(any(ParcelableCall.class));
-
-        // Give the InCallService time to respond
-
-        assertTrueWithTimeout(new Predicate<Void>() {
-            @Override
-            public boolean apply(Void v) {
-                return startingNumConnections + 1 ==
-                        connectionServiceFixture.mConnectionById.size();
-            }
-        });
-        assertTrueWithTimeout(new Predicate<Void>() {
-            @Override
-            public boolean apply(Void v) {
-                return startingNumCalls + 1 == mInCallServiceFixtureX.mCallById.size();
-            }
-        });
-        assertTrueWithTimeout(new Predicate<Void>() {
-            @Override
-            public boolean apply(Void v) {
-                return startingNumCalls + 1 == mInCallServiceFixtureY.mCallById.size();
-            }
-        });
-
-        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
-
         return new IdPair(connectionServiceFixture.mLatestConnectionId,
                 mInCallServiceFixtureX.mLatestCallId);
     }
@@ -903,17 +922,22 @@
                 Process.myUserHandle(), videoState);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
-        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_DIALING,
+                    mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_DIALING,
+                    mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        }
 
         connectionServiceFixture.sendSetVideoState(ids.mConnectionId);
 
         when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CONNECT_TIME);
         when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CONNECT_ELAPSED_TIME);
         connectionServiceFixture.sendSetActive(ids.mConnectionId);
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
-
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        }
         return ids;
     }
 
@@ -933,26 +957,32 @@
             int videoState) throws Exception {
         IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
 
-        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_RINGING,
+                    mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_RINGING,
+                    mInCallServiceFixtureY.getCall(ids.mCallId).getState());
 
-        mInCallServiceFixtureX.mInCallAdapter
-                .answerCall(ids.mCallId, videoState);
+            mInCallServiceFixtureX.mInCallAdapter
+                    .answerCall(ids.mCallId, videoState);
 
-        if (!VideoProfile.isVideo(videoState)) {
-            verify(connectionServiceFixture.getTestDouble())
-                    .answer(eq(ids.mConnectionId), any());
-        } else {
-            verify(connectionServiceFixture.getTestDouble())
-                    .answerVideo(eq(ids.mConnectionId), eq(videoState), any());
+            if (!VideoProfile.isVideo(videoState)) {
+                verify(connectionServiceFixture.getTestDouble())
+                        .answer(eq(ids.mConnectionId), any());
+            } else {
+                verify(connectionServiceFixture.getTestDouble())
+                        .answerVideo(eq(ids.mConnectionId), eq(videoState), any());
+            }
         }
 
         when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CONNECT_TIME);
         when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CONNECT_ELAPSED_TIME);
         connectionServiceFixture.sendSetActive(ids.mConnectionId);
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
 
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        }
         return ids;
     }