cleanupOrphanPA's feature (1/2)*

Bug: 210134615
Test: Unit + manual
Change-Id: Ide6fc11a3ce8b2d1a911c4886f07f383db9424bc
Merged-In: Ide6fc11a3ce8b2d1a911c4886f07f383db9424bc
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index d8f2d22..4e8524e 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -76,6 +76,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -1187,6 +1188,53 @@
         return accounts;
     }
 
+    /**
+     * Clean up the orphan {@code PhoneAccount}. An orphan {@code PhoneAccount} is a phone
+     * account that does not have a {@code UserHandle} or belongs to a deleted package.
+     *
+     * @return the number of orphan {@code PhoneAccount} deleted.
+     */
+    public int cleanupOrphanedPhoneAccounts() {
+        ArrayList<PhoneAccount> badAccountsList = new ArrayList<>();
+        HashMap<String, Boolean> packageLookup = new HashMap<>();
+        HashMap<PhoneAccount, Boolean> userHandleLookup = new HashMap<>();
+
+        // iterate over all accounts in registrar
+        for (PhoneAccount pa : mState.accounts) {
+            String packageName = pa.getAccountHandle().getComponentName().getPackageName();
+
+            // check if the package for the PhoneAccount is uninstalled
+            if (packageLookup.computeIfAbsent(packageName,
+                    pn -> isPackageUninstalled(pn))) {
+                badAccountsList.add(pa);
+            }
+            // check if PhoneAccount does not have a valid UserHandle (user was deleted)
+            else if (userHandleLookup.computeIfAbsent(pa,
+                    a -> isUserHandleDeletedForPhoneAccount(a))) {
+                badAccountsList.add(pa);
+            }
+        }
+
+        mState.accounts.removeAll(badAccountsList);
+
+        return badAccountsList.size();
+    }
+
+    public Boolean isPackageUninstalled(String packageName) {
+        try {
+            mContext.getPackageManager().getPackageInfo(packageName, 0);
+            return false;
+        } catch (PackageManager.NameNotFoundException e) {
+            return true;
+        }
+    }
+
+    private Boolean isUserHandleDeletedForPhoneAccount(PhoneAccount phoneAccount) {
+        UserHandle userHandle = phoneAccount.getAccountHandle().getUserHandle();
+        return (userHandle == null) ||
+                (mUserManager.getSerialNumberForUser(userHandle) == -1L);
+    }
+
     //
     // State Implementation for PhoneAccountRegistrar
     //
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 80bcbe0..c765a6e 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1875,6 +1875,31 @@
         }
 
         /**
+         * A method intended for test to clean up orphan {@link PhoneAccount}. An orphan
+         * {@link PhoneAccount} is a phone account belongs to an invalid {@link UserHandle} or a
+         * deleted package.
+         *
+         * @return the number of orphan {@code PhoneAccount} deleted.
+         */
+        @Override
+        public int cleanupOrphanPhoneAccounts() {
+            Log.startSession("TCI.cOPA");
+            try {
+                synchronized (mLock) {
+                    enforceShellOnly(Binder.getCallingUid(), "cleanupOrphanPhoneAccounts");
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        return mPhoneAccountRegistrar.cleanupOrphanedPhoneAccounts();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
          * A method intended for use in testing to reset car mode at all priorities.
          *
          * Runs during setup to avoid cascading failures from failing car mode CTS.
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index a56036a..6232396 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -28,6 +28,8 @@
 
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.graphics.BitmapFactory;
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
@@ -82,6 +84,9 @@
     private static final int MAX_VERSION = Integer.MAX_VALUE;
     private static final String FILE_NAME = "phone-account-registrar-test-1223.xml";
     private static final String TEST_LABEL = "right";
+    private final String PACKAGE_1 = "PACKAGE_1";
+    private final String PACKAGE_2 = "PACKAGE_2";
+    private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
     private PhoneAccountRegistrar mRegistrar;
     @Mock private TelecomManager mTelecomManager;
     @Mock private DefaultDialerCache mDefaultDialerCache;
@@ -1015,6 +1020,52 @@
         assertFalse(PhoneAccountHandle.areFromSamePackage(null, d));
     }
 
+    /**
+     * Tests {@link PhoneAccountRegistrar#cleanupOrphanedPhoneAccounts } cleans up / deletes an
+     * orphan account.
+     */
+    @Test
+    public void testCleanUpOrphanAccounts() throws Exception {
+        // GIVEN
+        mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
+                Mockito.mock(IConnectionService.class));
+
+        List<UserHandle> users = Arrays.asList(new UserHandle(0),
+                new UserHandle(1000));
+
+        PhoneAccount pa1 = new PhoneAccount.Builder(
+                new PhoneAccountHandle(new ComponentName(PACKAGE_1, COMPONENT_NAME), "1234",
+                        users.get(0)), "l1").build();
+        PhoneAccount pa2 = new PhoneAccount.Builder(
+                new PhoneAccountHandle(new ComponentName(PACKAGE_2, COMPONENT_NAME), "5678",
+                        users.get(1)), "l2").build();
+
+
+        registerAndEnableAccount(pa1);
+        registerAndEnableAccount(pa2);
+
+        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(0)).size());
+        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(1)).size());
+
+
+        // WHEN
+        when(mContext.getPackageManager().getPackageInfo(PACKAGE_1, 0))
+                .thenReturn(new PackageInfo());
+
+        when(mContext.getPackageManager().getPackageInfo(PACKAGE_2, 0))
+                .thenThrow(new PackageManager.NameNotFoundException());
+
+        when(UserManager.get(mContext).getSerialNumberForUser(users.get(0)))
+                .thenReturn(0L);
+
+        when(UserManager.get(mContext).getSerialNumberForUser(users.get(1)))
+                .thenReturn(-1L);
+
+        // THEN
+        int deletedAccounts = mRegistrar.cleanupOrphanedPhoneAccounts();
+        assertEquals(1, deletedAccounts);
+    }
+
     private static ComponentName makeQuickConnectionServiceComponentName() {
         return new ComponentName(
                 "com.android.server.telecom.tests",