Merge "isInSelfManagedCall for (package,user) should return true for calls being setup" into udc-dev am: 6060f0ce0d

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telecomm/+/23697350

Change-Id: Ie537855d773ec44ff935bd661b77aff0cdaf4c03
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index eb86ad0..4d2f273 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -353,6 +353,16 @@
             new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
 
     /**
+     * List of self-managed calls that have been initialized but not yet added to
+     * CallsManager#addCall(Call). There is a window of time when a Call has been added to Telecom
+     * (e.g. TelecomManager#addNewIncomingCall) to actually added in CallsManager#addCall(Call).
+     * This list is helpful for the NotificationManagerService to know that Telecom is currently
+     * setting up a call which is an important set in making notifications non-dismissible.
+     */
+    private final Set<Call> mSelfManagedCallsBeingSetup = Collections.newSetFromMap(
+            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
+
+    /**
      * A pending call is one which requires user-intervention in order to be placed.
      * Used by {@link #startCallConfirmation}.
      */
@@ -1398,6 +1408,8 @@
             // Required for backwards compatibility
             handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
         }
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+                phoneAccountHandle);
         Call call = new Call(
                 generateNextCallId(extras),
                 mContext,
@@ -1414,6 +1426,15 @@
                 isConference, /* isConference */
                 mClockProxy,
                 mToastFactory);
+        // Ensure new calls related to self-managed calls/connections are set as such. This will
+        // be overridden when the actual connection is returned in startCreateConnection, however
+        // doing this now ensures the logs and any other logic will treat this call as self-managed
+        // from the moment it is created.
+        boolean isSelfManaged = phoneAccount != null && phoneAccount.isSelfManaged();
+        call.setIsSelfManaged(isSelfManaged);
+        // It's important to start tracking self-managed calls as soon as the Call object is
+        // initialized so NotificationManagerService is aware Telecom is setting up a call
+        if (isSelfManaged) mSelfManagedCallsBeingSetup.add(call);
 
         // set properties for transactional call
         if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
@@ -1434,15 +1455,8 @@
             call.setAssociatedUser(phoneAccountHandle.getUserHandle());
         }
 
-        // Ensure new calls related to self-managed calls/connections are set as such. This will
-        // be overridden when the actual connection is returned in startCreateConnection, however
-        // doing this now ensures the logs and any other logic will treat this call as self-managed
-        // from the moment it is created.
-        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);
@@ -1745,7 +1759,7 @@
                     isConference ? participants : null,
                     null /* gatewayInfo */,
                     null /* connectionManagerPhoneAccount */,
-                    null /* requestedAccountHandle */,
+                    requestedAccountHandle /* targetPhoneAccountHandle */,
                     Call.CALL_DIRECTION_OUTGOING /* callDirection */,
                     false /* forceAttachToExistingConnection */,
                     isConference, /* isConference */
@@ -1766,7 +1780,6 @@
                                 TelecomManager.PRESENTATION_ALLOWED);
                     }
                 }
-                call.setTargetPhoneAccount(requestedAccountHandle);
             }
 
             call.initAnalytics(callingPackage, creationLogs.toString());
@@ -1805,6 +1818,9 @@
         } else {
             isReusedCall = true;
         }
+        // It's important to start tracking self-managed calls as soon as the Call object is
+        // initialized so NotificationManagerService is aware Telecom is setting up a call
+        if (isSelfManaged) mSelfManagedCallsBeingSetup.add(call);
 
         int videoState = VideoProfile.STATE_AUDIO_ONLY;
         if (extras != null) {
@@ -4241,6 +4257,7 @@
         Log.i(this, "addCall(%s)", call);
         call.addListener(this);
         mCalls.add(call);
+        mSelfManagedCallsBeingSetup.remove(call);
 
         // Specifies the time telecom finished routing the call. This is used by the dialer for
         // analytics.
@@ -4284,6 +4301,7 @@
             mCalls.remove(call);
             shouldNotify = true;
         }
+        mSelfManagedCallsBeingSetup.remove(call);
 
         call.destroy();
         updateExternalCallCanPullSupport();
@@ -4541,8 +4559,10 @@
      * @return {@code true} if the app has ongoing calls, or {@code false} otherwise.
      */
     public boolean isInSelfManagedCall(String packageName, UserHandle userHandle) {
-        return mCalls.stream().anyMatch(
-                c -> c.isSelfManaged()
+        return mSelfManagedCallsBeingSetup.stream().anyMatch(c -> c.isSelfManaged()
+                && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
+                && c.getTargetPhoneAccount().getUserHandle().equals(userHandle)) ||
+                mCalls.stream().anyMatch(c -> c.isSelfManaged()
                 && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
                 && c.getTargetPhoneAccount().getUserHandle().equals(userHandle));
     }
@@ -4734,11 +4754,14 @@
     }
 
     /**
-     * Determines if there are any self-managed calls.
+     * Note: isInSelfManagedCall(packageName, UserHandle) should always be used in favor or this
+     * method. This method determines if there are any self-managed calls globally.
      * @return {@code true} if there are self-managed calls, {@code false} otherwise.
      */
+    @VisibleForTesting
     public boolean hasSelfManagedCalls() {
-        return mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
+        return mSelfManagedCallsBeingSetup.size() > 0 ||
+                mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
     }
 
     /**
@@ -6493,4 +6516,14 @@
         }
         call.getTransactionServiceWrapper().stopCallStreaming(call);
     }
+
+    @VisibleForTesting
+    public Set<Call> getSelfManagedCallsBeingSetup() {
+        return mSelfManagedCallsBeingSetup;
+    }
+
+    @VisibleForTesting
+    public void addCallBeingSetup(Call call) {
+        mSelfManagedCallsBeingSetup.add(call);
+    }
 }
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
index 707c325..50556a1 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
@@ -251,6 +251,7 @@
                     @Override
                     public void onResult(CallControl callControl) {
                         Log.i(TAG, "addCall: onResult: callback fired");
+                        Utils.postIncomingCallStyleNotification(getApplicationContext());
                         mVoipCall.onAddCallControl(callControl);
                         updateCallId();
                         updateCurrentEndpoint();
@@ -275,7 +276,8 @@
         mAudioRecord.stop();
         try {
             mAudioRecord.unregisterAudioRecordingCallback(mAudioRecordingCallback);
-        } catch (IllegalArgumentException e) {
+            Utils.clearNotification(getApplicationContext());
+        } catch (Exception e) {
             // pass through
         }
     }
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
index 0de2b19..ec448b2 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
@@ -77,11 +77,16 @@
                         pendingAnswer, pendingReject)
                 )
                 .setFullScreenIntent(pendingAnswer, true)
+                .setOngoing(true)
                 .build();
 
         return callStyleNotification;
     }
 
+    public static void postIncomingCallStyleNotification(Context context) {
+        NotificationManager nm = context.getSystemService(NotificationManager.class);
+        nm.notify(Utils.CALL_NOTIFICATION_ID, createCallStyleNotification(context));
+    }
 
     public static void updateCallStyleNotification_toOngoingCall(Context context) {
         PendingIntent ongoingCall = PendingIntent.getActivity(context, 0,
@@ -97,6 +102,7 @@
                         ongoingCall)
                 )
                 .setFullScreenIntent(ongoingCall, true)
+                .setOngoing(true)
                 .build();
 
         NotificationManager notificationManager =
@@ -105,6 +111,14 @@
         notificationManager.notify(CALL_NOTIFICATION_ID, callStyleNotification);
     }
 
+    public static void clearNotification(Context context) {
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        if (notificationManager != null) {
+            notificationManager.cancel(CALL_NOTIFICATION_ID);
+        }
+    }
+
     public static MediaPlayer createMediaPlayer(Context context) {
         int audioToPlay = (Math.random() > 0.5f) ?
                 com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio :
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
index 7578b9d..72a3906 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
@@ -99,8 +99,6 @@
     }
 
     private void startInCallActivity(int direction) {
-        mNotificationManager.notify(Utils.CALL_NOTIFICATION_ID,
-                Utils.createCallStyleNotification(getApplicationContext()));
         Bundle extras = new Bundle();
         extras.putInt(Utils.sCALL_DIRECTION_KEY, direction);
         Intent intent = new Intent(getApplicationContext(), InCallActivity.class);
@@ -142,6 +140,7 @@
     protected void onDestroy() {
         Log.i(TAG, ACT_STATE_TAG + " onDestroy: is called before the activity is"
                 + " destroyed. ");
+        Utils.clearNotification(getApplicationContext());
         super.onDestroy();
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 7f252bc..c42a2ca 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -62,7 +62,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.BlockedNumberContract;
-import android.provider.Telephony;
 import android.telecom.CallException;
 import android.telecom.CallScreeningService;
 import android.telecom.CallerInfo;
@@ -156,6 +155,8 @@
     private static final int TEST_TIMEOUT = 5000;  // milliseconds
     private static final long STATE_TIMEOUT = 5000L;
     private static final int SECONDARY_USER_ID = 12;
+    private static final UserHandle TEST_USER_HANDLE = UserHandle.of(123);
+    private static final String TEST_PACKAGE_NAME = "GoogleMeet";
     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(
@@ -173,6 +174,8 @@
             ComponentName.unflattenFromString("com.baz/.Self"), "Self");
     private static final PhoneAccountHandle SELF_MANAGED_2_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.baz/.Self2"), "Self2");
+    private static final PhoneAccountHandle SELF_MANAGED_W_CUSTOM_HANDLE = new PhoneAccountHandle(
+            new ComponentName(TEST_PACKAGE_NAME, "class"), "1", TEST_USER_HANDLE);
     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
                     | PhoneAccount.CAPABILITY_CALL_PROVIDER
@@ -202,6 +205,11 @@
             .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
             .setIsEnabled(true)
             .build();
+    private static final PhoneAccount SM_W_DIFFERENT_PACKAGE_AND_USER = new PhoneAccount.Builder(
+            SELF_MANAGED_W_CUSTOM_HANDLE, "Self")
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .setIsEnabled(true)
+            .build();
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
     private static final Uri TEST_ADDRESS2 = Uri.parse("tel:555-1213");
     private static final Uri TEST_ADDRESS3 = Uri.parse("tel:555-1214");
@@ -3078,6 +3086,109 @@
                 eq(false));
     }
 
+    /**
+     * Verify CallsManager#isInSelfManagedCall(packageName, userHandle) returns true when
+     * CallsManager is first made aware of the incoming call in processIncomingCallIntent.
+     */
+    @SmallTest
+    @Test
+    public void testAddNewIncomingCall_IsInSelfManagedCall() {
+        // GIVEN
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+
+        // WHEN
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any()))
+                .thenReturn(SM_W_DIFFERENT_PACKAGE_AND_USER);
+
+        // THEN
+        mCallsManager.processIncomingCallIntent(SELF_MANAGED_W_CUSTOM_HANDLE, new Bundle(), false);
+
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        assertEquals(0, mCallsManager.getCalls().size());
+    }
+
+    /**
+     * Verify CallsManager#isInSelfManagedCall(packageName, userHandle) returns true when
+     * CallsManager is first made aware of the outgoing call in StartOutgoingCall.
+     */
+    @SmallTest
+    @Test
+    public void testStartOutgoing_IsInSelfManagedCall() {
+        // GIVEN
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+
+        // WHEN
+        when(mPhoneAccountRegistrar.getPhoneAccount(any(), any()))
+                .thenReturn(SM_W_DIFFERENT_PACKAGE_AND_USER);
+        // Ensure contact info lookup succeeds
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+        // Ensure we have candidate phone account handle info.
+        when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
+                SELF_MANAGED_W_CUSTOM_HANDLE);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
+                new ArrayList<>(List.of(SELF_MANAGED_W_CUSTOM_HANDLE)));
+
+        // THEN
+        mCallsManager.startOutgoingCall(TEST_ADDRESS, SELF_MANAGED_W_CUSTOM_HANDLE, new Bundle(),
+                TEST_USER_HANDLE, new Intent(), TEST_PACKAGE_NAME);
+
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        assertEquals(0, mCallsManager.getCalls().size());
+    }
+
+    /**
+     * Verify SelfManagedCallsBeingSetup is being cleaned up in CallsManager#addCall and
+     * CallsManager#removeCall.  This ensures no memory leaks.
+     */
+    @SmallTest
+    @Test
+    public void testCallsBeingSetupCleanup() {
+        Call spyCall = addSpyCall();
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // verify CallsManager#removeCall removes the call from SelfManagedCallsBeingSetup
+        mCallsManager.addCallBeingSetup(spyCall);
+        mCallsManager.removeCall(spyCall);
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // verify CallsManager#addCall removes the call from SelfManagedCallsBeingSetup
+        mCallsManager.addCallBeingSetup(spyCall);
+        mCallsManager.addCall(spyCall);
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+    }
+
+    /**
+     * Verify isInSelfManagedCall returns false if there is a self-managed call, but it is for a
+     * different package and user
+     */
+    @SmallTest
+    @Test
+    public void testIsInSelfManagedCall_PackageUserQueryIsWorkingAsIntended() {
+        // start an active call
+        Call randomCall = createSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        mCallsManager.addCallBeingSetup(randomCall);
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // query isInSelfManagedCall for a package that is NOT in a call;  expect false
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        // start another call
+        Call targetCall = addSpyCall(SELF_MANAGED_W_CUSTOM_HANDLE, CallState.DIALING);
+        when(targetCall.getTargetPhoneAccount()).thenReturn(SELF_MANAGED_W_CUSTOM_HANDLE);
+        when(targetCall.isSelfManaged()).thenReturn(true);
+        mCallsManager.addCallBeingSetup(targetCall);
+        // query isInSelfManagedCall for a package that is in a call
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+    }
+
+
     private Call addSpyCall() {
         return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
     }