Merge "Keep the BT devices cache of BluetoothDeviceManager updated" into main
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
index b060ed0..5d42b86 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -16,3 +16,14 @@
   description: "If a self-managed call is stuck in certain states, disconnect it"
   bug: "360298368"
 }
+
+# OWNER=tgunn TARGET=25Q2
+flag {
+  name: "dont_timeout_destroyed_calls"
+  namespace: "telecom"
+  description: "When create connection timeout is hit, if call is already destroyed, skip anomaly"
+  bug: "381684580"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
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/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 727d300..04f1934 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -1050,7 +1050,8 @@
             mStatusBarNotifier.notifySpeakerphone(mCallsManager.hasAnyCalls());
         } else {
             if (mSpeakerDockRoute != null && getCallSupportedRoutes().contains(mSpeakerDockRoute)
-                    && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER) {
+                    && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER
+                    && mCurrentRoute.getType() != AudioRoute.TYPE_SPEAKER) {
                 routeTo(mIsActive, mSpeakerDockRoute);
                 // Since the route switching triggered by this message, we need to manually send it
                 // again so that we won't stuck in the pending route
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 14c8f62..260c238 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -130,11 +130,7 @@
                 synchronized (mLock) {
                     logIncoming("handleCreateConnectionComplete %s", callId);
                     Call call = mCallIdMapper.getCall(callId);
-                    if (call != null && mScheduledFutureMap.containsKey(call)) {
-                        ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
-                        existingTimeout.cancel(false /* cancelIfRunning */);
-                        mScheduledFutureMap.remove(call);
-                    }
+                    maybeRemoveCleanupFuture(call);
                     // Check status hints image for cross user access
                     if (connection.getStatusHints() != null) {
                         Icon icon = connection.getStatusHints().getIcon();
@@ -174,11 +170,7 @@
                 synchronized (mLock) {
                     logIncoming("handleCreateConferenceComplete %s", callId);
                     Call call = mCallIdMapper.getCall(callId);
-                    if (call != null && mScheduledFutureMap.containsKey(call)) {
-                        ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
-                        existingTimeout.cancel(false /* cancelIfRunning */);
-                        mScheduledFutureMap.remove(call);
-                    }
+                    maybeRemoveCleanupFuture(call);
                     // Check status hints image for cross user access
                     if (conference.getStatusHints() != null) {
                         Icon icon = conference.getStatusHints().getIcon();
@@ -1678,6 +1670,9 @@
                             Log.getExternalSession(TELECOM_ABBREVIATION));
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConference -- %s", getComponentName());
+                    if (mFlags.dontTimeoutDestroyedCalls()) {
+                        maybeRemoveCleanupFuture(call);
+                    }
                     mPendingResponses.remove(callId).handleCreateConferenceFailure(
                             new DisconnectCause(DisconnectCause.ERROR, e.toString()));
                 }
@@ -1708,6 +1703,9 @@
                     Log.i(ConnectionServiceWrapper.this, "Call not present"
                             + " in call id mapper, maybe it was aborted before the bind"
                             + " completed successfully?");
+                    if (mFlags.dontTimeoutDestroyedCalls()) {
+                        maybeRemoveCleanupFuture(call);
+                    }
                     response.handleCreateConnectionFailure(
                             new DisconnectCause(DisconnectCause.CANCELED));
                     return;
@@ -1793,6 +1791,9 @@
                 mScheduledFutureMap.put(call, future);
                 try {
                     if (mFlags.cswServiceInterfaceIsNull() && mServiceInterface == null) {
+                        if (mFlags.dontTimeoutDestroyedCalls()) {
+                            maybeRemoveCleanupFuture(call);
+                        }
                         mPendingResponses.remove(callId).handleCreateConnectionFailure(
                                 new DisconnectCause(DisconnectCause.ERROR,
                                         "CSW#oCC ServiceInterface is null"));
@@ -1807,6 +1808,9 @@
                     }
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
+                    if (mFlags.dontTimeoutDestroyedCalls()) {
+                        maybeRemoveCleanupFuture(call);
+                    }
                     mPendingResponses.remove(callId).handleCreateConnectionFailure(
                             new DisconnectCause(DisconnectCause.ERROR, e.toString()));
                 }
@@ -2286,6 +2290,9 @@
         if (response != null) {
             response.handleCreateConnectionFailure(disconnectCause);
         }
+        if (mFlags.dontTimeoutDestroyedCalls()) {
+            maybeRemoveCleanupFuture(mCallIdMapper.getCall(callId));
+        }
 
         mCallIdMapper.removeCall(callId);
     }
@@ -2295,6 +2302,9 @@
         if (response != null) {
             response.handleCreateConnectionFailure(disconnectCause);
         }
+        if (mFlags.dontTimeoutDestroyedCalls()) {
+            maybeRemoveCleanupFuture(call);
+        }
 
         mCallIdMapper.removeCall(call);
     }
@@ -2754,4 +2764,20 @@
     public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
         mAnomalyReporter = mAnomalyReporterAdapter;
     }
+
+    /**
+     * Given a call, unschedule and cancel the cleanup future.
+     * @param call the call.
+     */
+    private void maybeRemoveCleanupFuture(Call call) {
+        if (call == null) {
+            return;
+        }
+        ScheduledFuture<?> future = mScheduledFutureMap.remove(call);
+        if (future == null) {
+            return;
+        }
+        future.cancel(false /* interrupt */);
+
+    }
 }
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/src/com/android/server/telecom/metrics/ApiStats.java b/src/com/android/server/telecom/metrics/ApiStats.java
index f0b5dc7..4b23e47 100644
--- a/src/com/android/server/telecom/metrics/ApiStats.java
+++ b/src/com/android/server/telecom/metrics/ApiStats.java
@@ -191,6 +191,8 @@
             Arrays.stream(mPulledAtoms.telecomApiStats).forEach(v -> data.add(
                     TelecomStatsLog.buildStatsEvent(getTag(),
                             v.getApiName(), v.getUid(), v.getApiResult(), v.getCount())));
+            mApiStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/AudioRouteStats.java b/src/com/android/server/telecom/metrics/AudioRouteStats.java
index 21624f1..4611b22 100644
--- a/src/com/android/server/telecom/metrics/AudioRouteStats.java
+++ b/src/com/android/server/telecom/metrics/AudioRouteStats.java
@@ -99,6 +99,8 @@
                     TelecomStatsLog.buildStatsEvent(getTag(),
                             v.getCallAudioRouteSource(), v.getCallAudioRouteDest(),
                             v.getSuccess(), v.getRevert(), v.getCount(), v.getAverageLatencyMs())));
+            mAudioRouteStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/CallStats.java b/src/com/android/server/telecom/metrics/CallStats.java
index 7ebeba6..8bdeffb 100644
--- a/src/com/android/server/telecom/metrics/CallStats.java
+++ b/src/com/android/server/telecom/metrics/CallStats.java
@@ -81,6 +81,8 @@
                             v.getCallDirection(), v.getExternalCall(), v.getEmergencyCall(),
                             v.getMultipleAudioAvailable(), v.getAccountType(), v.getUid(),
                             v.getCount(), v.getAverageDurationMs())));
+            mCallStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/ErrorStats.java b/src/com/android/server/telecom/metrics/ErrorStats.java
index f70f6d8..f334710 100644
--- a/src/com/android/server/telecom/metrics/ErrorStats.java
+++ b/src/com/android/server/telecom/metrics/ErrorStats.java
@@ -140,6 +140,8 @@
             Arrays.stream(mPulledAtoms.telecomErrorStats).forEach(v -> data.add(
                     TelecomStatsLog.buildStatsEvent(getTag(),
                             v.getSubmodule(), v.getError(), v.getCount())));
+            mErrorStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
index 8ae734c..d3c7859 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
@@ -207,12 +207,14 @@
         createTestFileForApiStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = apiStats.mPulledAtoms.telecomApiStats.length;
 
         int result = apiStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(apiStats).onPull(eq(data));
-        assertEquals(data.size(), apiStats.mPulledAtoms.telecomApiStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, 0);
     }
 
     @Test
@@ -233,12 +235,14 @@
         createTestFileForAudioRouteStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = audioRouteStats.mPulledAtoms.callAudioRouteStats.length;
 
         int result = audioRouteStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(audioRouteStats).onPull(eq(data));
-        assertEquals(data.size(), audioRouteStats.mPulledAtoms.callAudioRouteStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(audioRouteStats.mPulledAtoms.callAudioRouteStats.length, 0);
     }
 
     @Test
@@ -259,12 +263,14 @@
         createTestFileForCallStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = callStats.mPulledAtoms.callStats.length;
 
         int result = callStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(callStats).onPull(eq(data));
-        assertEquals(data.size(), callStats.mPulledAtoms.callStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(callStats.mPulledAtoms.callStats.length, 0);
     }
 
     @Test
@@ -285,12 +291,14 @@
         createTestFileForErrorStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = errorStats.mPulledAtoms.telecomErrorStats.length;
 
         int result = errorStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(errorStats).onPull(eq(data));
-        assertEquals(data.size(), errorStats.mPulledAtoms.telecomErrorStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, 0);
     }
 
     @Test
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,