Optimize calls to TelephonyManager to check for privileged apps

Calling into TelephonyManager each of hundreds of apps to check
if the app is carrier privileged was very expensive, especially
when there aren't even any carrier access rules specified. This
change fetches all the carrier privileged apps in one call,
reducing the number of IPC calls to the radio process and checks
the package names locally.

If the carrier rules change or packages are modified, the list
will be computed and fetched again.

Other optimizations in Telephony help speed up the individual calls
to check if a package is privileged, as well.

Bug: 27271861
Change-Id: I5a77b6da4f2cdc603d2a73bd8569c5c38f06b42d
diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl
index a9328bc..342c285 100644
--- a/core/java/android/app/usage/IUsageStatsManager.aidl
+++ b/core/java/android/app/usage/IUsageStatsManager.aidl
@@ -33,4 +33,5 @@
     void setAppInactive(String packageName, boolean inactive, int userId);
     boolean isAppInactive(String packageName, int userId);
     void whitelistAppTemporarily(String packageName, long duration, int userId);
+    void onCarrierPrivilegedAppsChanged();
 }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index c74b0f2..2aeecfa 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -267,4 +267,15 @@
         } catch (RemoteException re) {
         }
     }
+
+    /**
+     * Inform usage stats that the carrier privileged apps access rules have changed.
+     * @hide
+     */
+    public void onCarrierPrivilegedAppsChanged() {
+        try {
+            mService.onCarrierPrivilegedAppsChanged();
+        } catch (RemoteException re) {
+        }
+    }
 }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 8e891bf..8b250f4 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -151,6 +151,8 @@
     private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
             mPackageAccessListeners = new ArrayList<>();
 
+    private List<String> mCarrierPrivilegedApps;
+
     public UsageStatsService(Context context) {
         super(context);
     }
@@ -170,10 +172,18 @@
                     + mUsageStatsDir.getAbsolutePath());
         }
 
-        IntentFilter userActions = new IntentFilter(Intent.ACTION_USER_REMOVED);
-        userActions.addAction(Intent.ACTION_USER_STARTED);
-        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, userActions,
-                null, null);
+        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_REMOVED);
+        filter.addAction(Intent.ACTION_USER_STARTED);
+        getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
+                null, mHandler);
+
+        IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        packageFilter.addDataScheme("package");
+
+        getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
+                null, mHandler);
 
         mAppIdleEnabled = getContext().getResources().getBoolean(
                 com.android.internal.R.bool.config_enableAutoPowerModes);
@@ -232,15 +242,15 @@
     }
 
     private class UserActionsReceiver extends BroadcastReceiver {
-
         @Override
         public void onReceive(Context context, Intent intent) {
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_USER_REMOVED.equals(action)) {
                 if (userId >= 0) {
                     mHandler.obtainMessage(MSG_REMOVE_USER, userId, 0).sendToTarget();
                 }
-            } else if (Intent.ACTION_USER_STARTED.equals(intent.getAction())) {
+            } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >=0) {
                     postCheckIdleStates(userId);
                 }
@@ -248,6 +258,17 @@
         }
     }
 
+    private class PackageReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                clearCarrierPrivilegedApps();
+            }
+        }
+    }
+
     private class DeviceStateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -890,9 +911,30 @@
     }
 
     private boolean isCarrierApp(String packageName) {
-        TelephonyManager telephonyManager = getContext().getSystemService(TelephonyManager.class);
-        return telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName)
-                    == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+        synchronized (mLock) {
+            if (mCarrierPrivilegedApps == null) {
+                fetchCarrierPrivilegedAppsLocked();
+            }
+        }
+        return mCarrierPrivilegedApps.contains(packageName);
+    }
+
+    void clearCarrierPrivilegedApps() {
+        if (DEBUG) {
+            Slog.i(TAG, "Clearing carrier privileged apps list");
+        }
+        synchronized (mLock) {
+            mCarrierPrivilegedApps = null; // Need to be refetched.
+        }
+    }
+
+    private void fetchCarrierPrivilegedAppsLocked() {
+        TelephonyManager telephonyManager =
+                getContext().getSystemService(TelephonyManager.class);
+        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
+        if (DEBUG) {
+            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
+        }
     }
 
     private boolean isActiveNetworkScorer(String packageName) {
@@ -963,6 +1005,9 @@
             }
 
             pw.println();
+            pw.println("Carrier privileged apps: " + mCarrierPrivilegedApps);
+
+            pw.println();
             pw.println("Settings:");
 
             pw.print("  mAppIdleDurationMillis=");
@@ -1257,6 +1302,17 @@
         }
 
         @Override
+        public void onCarrierPrivilegedAppsChanged() {
+            if (DEBUG) {
+                Slog.i(TAG, "Carrier privileged apps changed");
+            }
+            getContext().enforceCallingOrSelfPermission(
+                    android.Manifest.permission.BIND_CARRIER_SERVICES,
+                    "onCarrierPrivilegedAppsChanged can only be called by privileged apps.");
+            UsageStatsService.this.clearCarrierPrivilegedApps();
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                     != PackageManager.PERMISSION_GRANTED) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fcb42a4..daffb88 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -47,6 +47,7 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.Collections;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -3857,6 +3858,21 @@
     }
 
     /** @hide */
+    public List<String> getPackagesWithCarrierPrivileges() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getPackagesWithCarrierPrivileges();
+            }
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "getPackagesWithCarrierPrivileges RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "getPackagesWithCarrierPrivileges NPE", ex);
+        }
+        return Collections.EMPTY_LIST;
+    }
+
+    /** @hide */
     @SystemApi
     public void dial(String number) {
         try {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 62f294c..ad511a7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1028,4 +1028,9 @@
      * @return {@code true} if the vibration is set for this PhoneAccount, {@code false} otherwise.
      */
     boolean isVoicemailVibrationEnabled(in PhoneAccountHandle accountHandle);
+
+    /**
+     * Returns a list of packages that have carrier privileges.
+     */
+    List<String> getPackagesWithCarrierPrivileges();
 }