Allow privileged apps to accept and end VOIP calls.

This change updates Telecom Service to allow privileged app invocations to accept and end VOIP calls. This functionality was requested by the accessibility team in order to support their talkback double tap gesture feature.

Bug: 353579043
Flag: com.android.server.telecom.flags.allow_system_apps_resolve_voip_calls
Test: atest TelecomServiceImplTest
Change-Id: I6da4ce827fc6c7fe732b52aaad67cff75494483d
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
index 75efdfa..2dfd878 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -73,3 +73,12 @@
   description: "Formalizes the getLastKnownCellIdentity API that Telecom reliees on as a system api"
   bug: "327454165"
 }
+
+# OWNER=grantmenke TARGET=25Q2
+flag {
+  name: "allow_system_apps_resolve_voip_calls"
+  is_exported: true
+  namespace: "telecom"
+  description: "Allow system apps such as accessibility to accept and end VOIP calls."
+  bug: "353579043"
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 5eb05dc..488524f 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -160,6 +160,7 @@
     private final FeatureFlags mFeatureFlags;
     private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
     private final TelecomMetricsController mMetricsController;
+    private final String mSystemUiPackageName;
     private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
     private final Context mContext;
     private final AppOpsManager mAppOpsManager;
@@ -1437,7 +1438,7 @@
 
                 // ensure the callingPackage is not spoofed
                 // skip check for privileged UIDs and throw SE if package does not match records
-                if (!isPrivilegedUid(callingPackage)
+                if (!isPrivilegedUid()
                         && !callingUidMatchesPackageManagerRecords(callingPackage)) {
                     EventLog.writeEvent(0x534e4554, "236813210", Binder.getCallingUid(),
                             "getCallStateUsingPackage");
@@ -1470,17 +1471,36 @@
             }
         }
 
-        private boolean isPrivilegedUid(String callingPackage) {
+        private boolean isPrivilegedUid() {
             int callingUid = Binder.getCallingUid();
-            boolean isPrivileged = false;
-            switch (callingUid) {
-                case Process.ROOT_UID:
-                case Process.SYSTEM_UID:
-                case Process.SHELL_UID:
-                    isPrivileged = true;
-                    break;
+            return mFeatureFlags.allowSystemAppsResolveVoipCalls()
+                    ? (UserHandle.isSameApp(callingUid, Process.ROOT_UID)
+                            || UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
+                            || UserHandle.isSameApp(callingUid, Process.SHELL_UID))
+                    : (callingUid == Process.ROOT_UID
+                            || callingUid == Process.SYSTEM_UID
+                            || callingUid == Process.SHELL_UID);
+        }
+
+        private boolean isSysUiUid() {
+            int callingUid = Binder.getCallingUid();
+            int systemUiUid;
+            if (mPackageManager != null && mSystemUiPackageName != null) {
+                try {
+                    systemUiUid = mPackageManager.getPackageUid(mSystemUiPackageName, 0);
+                    Log.i(TAG, "isSysUiUid: callingUid = " + callingUid + "; systemUiUid = "
+                            + systemUiUid);
+                    return UserHandle.isSameApp(callingUid, systemUiUid);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, "isSysUiUid: caught PackageManager NameNotFoundException = " + e);
+                    return false;
+                }
+            } else {
+                Log.w(TAG, "isSysUiUid: caught null check and returned false; "
+                        + "mPackageManager = " + mPackageManager + "; mSystemUiPackageName = "
+                        + mSystemUiPackageName);
             }
-            return isPrivileged;
+            return false;
         }
 
         /**
@@ -1496,11 +1516,18 @@
                     if (!enforceAnswerCallPermission(callingPackage, Binder.getCallingUid())) {
                         throw new SecurityException("requires ANSWER_PHONE_CALLS permission");
                     }
-
+                    // Legacy behavior is to ignore whether the invocation is from a system app:
+                    boolean isCallerPrivileged = false;
+                    if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+                        isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+                        Log.i(TAG, "endCall: Binder.getCallingUid = [" +
+                                Binder.getCallingUid() + "] isCallerPrivileged = " +
+                                isCallerPrivileged);
+                    }
                     long token = Binder.clearCallingIdentity();
                     event.setResult(ApiStats.RESULT_NORMAL);
                     try {
-                        return endCallInternal(callingPackage);
+                        return endCallInternal(callingPackage, isCallerPrivileged);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -1522,11 +1549,19 @@
                 Log.startSession("TSI.aRC", Log.getPackageAbbreviation(packageName));
                 synchronized (mLock) {
                     if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return;
-
+                    // Legacy behavior is to ignore whether the invocation is from a system app:
+                    boolean isCallerPrivileged = false;
+                    if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+                        isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+                        Log.i(TAG, "acceptRingingCall: Binder.getCallingUid = [" +
+                                Binder.getCallingUid() + "] isCallerPrivileged = " +
+                                isCallerPrivileged);
+                    }
                     long token = Binder.clearCallingIdentity();
                     event.setResult(ApiStats.RESULT_NORMAL);
                     try {
-                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName);
+                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName,
+                                isCallerPrivileged);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -1549,11 +1584,18 @@
                 Log.startSession("TSI.aRCWVS", Log.getPackageAbbreviation(packageName));
                 synchronized (mLock) {
                     if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return;
-
+                    // Legacy behavior is to ignore whether the invocation is from a system app:
+                    boolean isCallerPrivileged = false;
+                    if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+                        isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+                        Log.i(TAG, "acceptRingingCallWithVideoState: Binder.getCallingUid = "
+                                + "[" + Binder.getCallingUid() + "] isCallerPrivileged = " +
+                                isCallerPrivileged);
+                    }
                     long token = Binder.clearCallingIdentity();
                     event.setResult(ApiStats.RESULT_NORMAL);
                     try {
-                        acceptRingingCallInternal(videoState, packageName);
+                        acceptRingingCallInternal(videoState, packageName, isCallerPrivileged);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -2918,7 +2960,8 @@
             SettingsSecureAdapter settingsSecureAdapter,
             FeatureFlags featureFlags,
             com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags,
-            TelecomSystem.SyncRoot lock, TelecomMetricsController metricsController) {
+            TelecomSystem.SyncRoot lock, TelecomMetricsController metricsController,
+            String sysUiPackageName) {
         mContext = context;
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
 
@@ -2940,6 +2983,7 @@
         mSubscriptionManagerAdapter = subscriptionManagerAdapter;
         mSettingsSecureAdapter = settingsSecureAdapter;
         mMetricsController = metricsController;
+        mSystemUiPackageName = sysUiPackageName;
 
         mDefaultDialerCache.observeDefaultDialerApplication(mContext.getMainExecutor(), userId -> {
             String defaultDialer = mDefaultDialerCache.getDefaultDialerApplication(userId);
@@ -3054,13 +3098,14 @@
         return false;
     }
 
-    private void acceptRingingCallInternal(int videoState, String packageName) {
+    private void acceptRingingCallInternal(int videoState, String packageName,
+            boolean isCallerPrivileged) {
         Call call = mCallsManager.getFirstCallWithState(CallState.RINGING,
                 CallState.SIMULATED_RINGING);
         if (call != null) {
-            if (call.isSelfManaged()) {
+            if (call.isSelfManaged() && !isCallerPrivileged) {
                 Log.addEvent(call, LogUtils.Events.REQUEST_ACCEPT,
-                        "self-mgd accept ignored from " + packageName);
+                        "self-mgd accept ignored from non-privileged app " + packageName);
                 return;
             }
 
@@ -3075,7 +3120,7 @@
     // Supporting methods for the ITelecomService interface implementation.
     //
 
-    private boolean endCallInternal(String callingPackage) {
+    private boolean endCallInternal(String callingPackage, boolean isCallerPrivileged) {
         // Always operate on the foreground call if one exists, otherwise get the first call in
         // priority order by call-state.
         Call call = mCallsManager.getForegroundCall();
@@ -3095,9 +3140,10 @@
                 return false;
             }
 
-            if (call.isSelfManaged()) {
+            if (call.isSelfManaged() && !isCallerPrivileged) {
                 Log.addEvent(call, LogUtils.Events.REQUEST_DISCONNECT,
-                        "self-mgd disconnect ignored from " + callingPackage);
+                        "self-mgd disconnect ignored from non-privileged app " +
+                                callingPackage);
                 return false;
             }
 
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index a714096..7020885 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -224,6 +224,7 @@
             RoleManagerAdapter roleManagerAdapter,
             ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
             DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+            String sysUiPackageName,
             Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
             Executor asyncTaskExecutor,
             Executor asyncCallAudioTaskExecutor,
@@ -503,7 +504,8 @@
                     featureFlags,
                     null,
                     mLock,
-                    metricsController);
+                    metricsController,
+                    sysUiPackageName);
         } finally {
             Log.endSession();
         }
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 2d8c78e..4db3e14 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -81,10 +81,11 @@
         Log.d(this, "onBind");
         return new ITelecomLoader.Stub() {
             @Override
-            public ITelecomService createTelecomService(IInternalServiceRetriever retriever) {
+            public ITelecomService createTelecomService(IInternalServiceRetriever retriever,
+                    String sysUiPackageName) {
                 InternalServiceRetrieverAdapter adapter =
                         new InternalServiceRetrieverAdapter(retriever);
-                initializeTelecomSystem(TelecomService.this, adapter);
+                initializeTelecomSystem(TelecomService.this, adapter, sysUiPackageName);
                 synchronized (getTelecomSystem().getLock()) {
                     return getTelecomSystem().getTelecomServiceImpl().getBinder();
                 }
@@ -103,7 +104,7 @@
      * @param context
      */
     static void initializeTelecomSystem(Context context,
-            InternalServiceRetrieverAdapter internalServiceRetriever) {
+            InternalServiceRetrieverAdapter internalServiceRetriever, String sysUiPackageName) {
         if (TelecomSystem.getInstance() == null) {
             FeatureFlags featureFlags = new FeatureFlagsImpl();
             NotificationChannelManager notificationChannelManager =
@@ -204,6 +205,7 @@
                                     (RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
                             new ContactsAsyncHelper.Factory(),
                             internalServiceRetriever.getDeviceIdleController(),
+                            sysUiPackageName,
                             new Ringer.AccessibilityManagerAdapter() {
                                 @Override
                                 public boolean startFlashNotificationSequence(
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 07a12c4..6b0555c 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -209,6 +209,7 @@
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
+    private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
     private static final String DEFAULT_DIALER_PACKAGE = "com.google.android.dialer";
     private static final UserHandle USER_HANDLE_16 = new UserHandle(16);
     private static final UserHandle USER_HANDLE_17 = new UserHandle(17);
@@ -260,7 +261,8 @@
                 mFeatureFlags,
                 mTelephonyFeatureFlags,
                 mLock,
-                mMockTelecomMetricsController);
+                mMockTelecomMetricsController,
+                SYSTEM_UI_PACKAGE);
         telecomServiceImpl.setTransactionManager(mTransactionManager);
         telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
         mTSIBinder = telecomServiceImpl.getBinder();
@@ -2307,7 +2309,8 @@
     }
 
     /**
-     * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()}.
+     * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()} when the
+     * caller of this method is not considered privileged.
      * @throws Exception
      */
     @SmallTest
@@ -2324,7 +2327,8 @@
 
     /**
      * Ensure self-managed calls cannot be answered using {@link TelecomManager#acceptRingingCall()}
-     * or {@link TelecomManager#acceptRingingCall(int)}.
+     * or {@link TelecomManager#acceptRingingCall(int)} when the caller of these methods is not
+     * considered privileged.
      * @throws Exception
      */
     @SmallTest
@@ -2339,6 +2343,53 @@
         verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt());
     }
 
+    /**
+     * Ensure self-managed calls can be answered using {@link TelecomManager#acceptRingingCall()}
+     * or {@link TelecomManager#acceptRingingCall(int)} if the caller of these methods is
+     * privileged.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testCanAnswerSelfManagedCallIfPrivileged() throws Exception {
+        when(mFeatureFlags.allowSystemAppsResolveVoipCalls()).thenReturn(true);
+        // Configure the test so that the caller of acceptRingingCall is considered privileged:
+        when(mPackageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0))
+                .thenReturn(Binder.getCallingUid());
+
+        // Ensure that the call is successfully accepted:
+        Call call = mock(Call.class);
+        when(call.isSelfManaged()).thenReturn(true);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getFirstCallWithState(any()))
+                .thenReturn(call);
+        mTSIBinder.acceptRingingCall(TEST_PACKAGE);
+        verify(mFakeCallsManager).answerCall(eq(call), anyInt());
+    }
+
+    /**
+     * Ensure self-managed calls can be ended using {@link TelecomManager#endCall()} when the
+     * caller of these methods is privileged.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testCanEndSelfManagedCallIfPrivileged() throws Exception {
+        when(mFeatureFlags.allowSystemAppsResolveVoipCalls()).thenReturn(true);
+        // Configure the test so that the caller of endCall is considered privileged:
+        when(mPackageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0))
+                .thenReturn(Binder.getCallingUid());
+        // Set up the call:
+        Call call = mock(Call.class);
+        when(call.isSelfManaged()).thenReturn(true);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getFirstCallWithState(any()))
+                .thenReturn(call);
+        // Ensure that the call is successfully ended:
+        assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
+        verify(mFakeCallsManager).disconnectCall(eq(call));
+    }
+
     @SmallTest
     @Test
     public void testGetAdnUriForPhoneAccount() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4463d65..1e65011 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -224,6 +224,7 @@
     @Mock
     com.android.internal.telephony.flags.FeatureFlags mTelephonyFlags;
 
+    private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
                     "incall-service-package-X",
@@ -580,7 +581,8 @@
                             ContactsAsyncHelper.ContentResolverAdapter adapter) {
                         return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
                     }
-                }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+                }, mDeviceIdleControllerAdapter, SYSTEM_UI_PACKAGE,
+                mAccessibilityManagerAdapter,
                 Runnable::run,
                 Runnable::run,
                 mBlockedNumbersAdapter,