Carrier privileges matching for CCL#dump.

It's possible for multiple apps to hold carrier privileges, but only one
can be the active CarrierService for a SIM. If package A is the
CarrierService but package B requests the bug report and also holds
carrier privileges, we should still dump package A as an approximate
equivalent to --requesting-package (package B).

Bug: 146521742
Test: dumpsys carrier_config --requesting-package <various>
Test: trigger bug report on user build from app with carrier privileges
Change-Id: I59d0e0ba3c8185ca8bfef0b94b2ab4bed210499f
Merged-In: I59d0e0ba3c8185ca8bfef0b94b2ab4bed210499f
(cherry picked from commit fd88fbcc1bf19d81a09aedc1c4a8719f1023360f)
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 1ccfe7b..b969ce4 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -50,6 +50,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -74,6 +75,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 /**
  * CarrierConfigLoader binds to privileged carrier apps to fetch carrier config overlays.
@@ -1101,8 +1103,18 @@
                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
             return;
         }
+        String requestingPackage = null;
+        int requestingPackageIndex = ArrayUtils.indexOf(args, DUMP_ARG_REQUESTING_PACKAGE);
+        if (requestingPackageIndex >= 0 && requestingPackageIndex < args.length - 1
+                && !TextUtils.isEmpty(args[requestingPackageIndex + 1])) {
+            requestingPackage = args[requestingPackageIndex + 1];
+            // Throws a SecurityException if the caller is impersonating another app in an effort to
+            // dump extra info (which may contain PII the caller doesn't have a right to).
+            enforceCallerIsSystemOrRequestingPackage(requestingPackage);
+        }
+
         indentPW.println("CarrierConfigLoader: " + this);
-        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+        for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount(); i++) {
             indentPW.println("Phone Id = " + i);
             // display default values in CarrierConfigManager
             printConfig(CarrierConfigManager.getDefaultConfig(), indentPW,
@@ -1118,20 +1130,15 @@
         indentPW.println("CarrierConfigLoadingLog=");
         mCarrierConfigLoadingLog.dump(fd, indentPW, args);
 
-        int requestingPackageIndex = ArrayUtils.indexOf(args, DUMP_ARG_REQUESTING_PACKAGE);
-        if (requestingPackageIndex != -1 && requestingPackageIndex < args.length - 1
-                && !TextUtils.isEmpty(args[requestingPackageIndex + 1])) {
-            String requestingPackage = args[requestingPackageIndex + 1];
-            indentPW.println("");
-            // Throws a SecurityException if the caller is impersonating another app in an effort to
-            // dump extra info (which may contain PII the caller doesn't have a right to).
-            enforceCallerIsSystemOrRequestingPackage(requestingPackage);
+        if (requestingPackage != null) {
             logd("Including default and requesting package " + requestingPackage
                     + " carrier services in dump");
+            indentPW.println("");
             indentPW.println("Connected services");
             dumpCarrierServiceIfBound(fd, indentPW, "Default config package",
-                    mPlatformCarrierConfigPackage);
-            dumpCarrierServiceIfBound(fd, indentPW, "Requesting package", requestingPackage);
+                    mPlatformCarrierConfigPackage, false /* considerCarrierPrivileges */);
+            dumpCarrierServiceIfBound(fd, indentPW, "Requesting package", requestingPackage,
+                    true /* considerCarrierPrivileges */);
         }
     }
 
@@ -1191,63 +1198,100 @@
         appOps.checkPackage(callingUid, requestingPackage);
     }
 
+    /**
+     * Searches for one or more appropriate {@link CarrierService} instances to dump based on the
+     * current connections.
+     *
+     * @param targetPkgName             the target package name to dump carrier services for
+     * @param considerCarrierPrivileges if true, allow a carrier service to be dumped if it shares
+     *                                  carrier privileges with {@code targetPkgName};
+     *                                  otherwise, only dump a carrier service if it is {@code
+     *                                  targetPkgName}
+     */
     private void dumpCarrierServiceIfBound(FileDescriptor fd, IndentingPrintWriter indentPW,
-            String prefix, String pkgName) {
+            String prefix, String targetPkgName, boolean considerCarrierPrivileges) {
         // Null package is possible if it's early in the boot process, there was a recent crash, we
         // loaded the config from XML most recently, or a SIM slot is empty. Carrier apps with
         // long-lived bindings should typically get dumped here regardless. Even if an app is being
         // used for multiple phoneIds, we assume that it's smart enough to handle that on its own,
         // and that in most cases we'd just be dumping duplicate information and bloating a report.
         indentPW.increaseIndent();
-        indentPW.println(prefix + " : " + pkgName);
+        indentPW.println(prefix + " : " + targetPkgName);
+        Set<String> dumpedPkgNames = new ArraySet<>(mServiceConnection.length);
         for (CarrierServiceConnection connection : mServiceConnection) {
-            try {
-                // We don't pay attention to mServiceBound[connection.phoneId] because typically
-                // carrier apps will request long-lived bindings, and even if we unbind the app, it
-                // may still be alive due to CarrierServiceBindHelper.
-                if (connection == null || connection.service == null || !TextUtils.equals(pkgName,
-                        connection.pkgName) || !connection.service.isBinderAlive()
-                        || !connection.service.pingBinder()) {
-                    continue;
-                }
-                // Flush before we let the app output anything to ensure correct ordering of output.
-                // Internally, Binder#dump calls flush on its printer after finishing so we don't
-                // need to after the call finishes.
-                indentPW.flush();
-                try {
-                    logd("Dumping " + pkgName);
-                    // We don't need to give the carrier service any args.
-                    connection.service.dump(fd, null /* args */);
-                    logd("Done with " + pkgName);
-                    indentPW.decreaseIndent();
-                    indentPW.println("");
-                    return;
-                } catch (RemoteException e) {
-                    indentPW.println("RemoteException");
-                    logd("RemoteException from " + pkgName, e);
-                    e.printStackTrace(indentPW);
-                    indentPW.decreaseIndent();
-                    indentPW.println("");
-                    return;
-                }
-            } catch (NullPointerException e) {
-                // Highly unlikely, but possible if the carrier app was just unbound right before we
-                // we tried to dump it so the binder was reset to null. Loop in case we have more
-                // candidates on other phoneIds.
-                logd("NullPointerException from " + pkgName, e);
+            if (connection == null || !SubscriptionManager.isValidPhoneId(connection.phoneId)
+                    || TextUtils.isEmpty(connection.pkgName)) {
+                continue;
             }
+            final String servicePkgName = connection.pkgName;
+            // Note: we intentionally ignore system components here because we should NOT match the
+            // shell caller that's typically used for bug reports via non-BugreportManager triggers.
+            final boolean exactPackageMatch = TextUtils.equals(targetPkgName, servicePkgName);
+            final boolean carrierPrivilegesMatch =
+                    considerCarrierPrivileges && hasCarrierPrivileges(targetPkgName,
+                            connection.phoneId);
+            if (!exactPackageMatch && !carrierPrivilegesMatch) continue;
+            // Make sure this service is actually alive before trying to dump it. We don't pay
+            // attention to mServiceBound[connection.phoneId] because typically carrier apps will
+            // request long-lived bindings, and even if we unbind the app, it may still be alive due
+            // to CarrierServiceBindHelper. Pull it out as a reference so even if it gets set to
+            // null within the ServiceConnection during unbinding we can avoid an NPE.
+            final IBinder service = connection.service;
+            if (service == null || !service.isBinderAlive() || !service.pingBinder()) continue;
+            // We've got a live service. Last check is just to make sure we don't dump a package
+            // multiple times.
+            if (!dumpedPkgNames.add(servicePkgName)) continue;
+            if (!exactPackageMatch) {
+                logd(targetPkgName + " has carrier privileges on phoneId " + connection.phoneId
+                        + ", service provided by " + servicePkgName);
+                indentPW.increaseIndent();
+                indentPW.println("Proxy : " + servicePkgName);
+                indentPW.decreaseIndent();
+            }
+            // Flush before we let the app output anything to ensure correct ordering of output.
+            // Internally, Binder#dump calls flush on its printer after finishing so we don't
+            // need to do anything after.
+            indentPW.flush();
+            try {
+                logd("Dumping " + servicePkgName);
+                // We don't need to give the carrier service any args.
+                connection.service.dump(fd, null /* args */);
+                logd("Done with " + servicePkgName);
+            } catch (RemoteException e) {
+                logd("RemoteException from " + servicePkgName, e);
+                indentPW.increaseIndent();
+                indentPW.println("RemoteException");
+                indentPW.increaseIndent();
+                e.printStackTrace(indentPW);
+                indentPW.decreaseIndent();
+                indentPW.decreaseIndent();
+                // We won't retry this package again because now it's in dumpedPkgNames.
+            }
+            indentPW.println("");
         }
-        indentPW.increaseIndent();
-        indentPW.println("Not bound");
+        if (dumpedPkgNames.isEmpty()) {
+            indentPW.increaseIndent();
+            indentPW.println("Not bound");
+            indentPW.decreaseIndent();
+            indentPW.println("");
+        }
         indentPW.decreaseIndent();
-        indentPW.decreaseIndent();
-        indentPW.println("");
+    }
+
+    private boolean hasCarrierPrivileges(String pkgName, int phoneId) {
+        int[] subIds = SubscriptionManager.getSubId(phoneId);
+        if (ArrayUtils.isEmpty(subIds)) {
+            return false;
+        }
+        return TelephonyManager.from(mContext).createForSubscriptionId(
+                subIds[0]).checkCarrierPrivilegesForPackage(pkgName)
+                == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
     }
 
     private class CarrierServiceConnection implements ServiceConnection {
-        int phoneId;
-        String pkgName;
-        int eventId;
+        final int phoneId;
+        final String pkgName;
+        final int eventId;
         IBinder service;
 
         CarrierServiceConnection(int phoneId, String pkgName, int eventId) {