Merge "Send special permission information to netd"
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 420b23e..d84a4d2 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -19,10 +19,11 @@
 import static android.Manifest.permission.CHANGE_NETWORK_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.INTERNET;
 import static android.Manifest.permission.NETWORK_STACK;
-import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
-import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.content.pm.PackageManager.MATCH_ANY_USER;
 import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 
@@ -32,23 +33,31 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
-import android.net.Uri;
+import android.net.INetd;
+import android.net.util.NetdService;
 import android.os.Build;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
+import android.util.Slog;
+import android.util.SparseIntArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map.Entry;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 
 /**
@@ -75,6 +84,59 @@
     // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission.
     private final Map<Integer, Boolean> mApps = new HashMap<>();
 
+    // Keys are App packageNames, Values are app uids. . We need to keep track of this information
+    // because PackageListObserver#onPackageRemoved does not pass the UID.
+    @GuardedBy("mPackageNameUidMap")
+    private final Map<String, Integer> mPackageNameUidMap = new HashMap<>();
+
+    private class PackageListObserver implements PackageManagerInternal.PackageListObserver {
+        @Override
+        public void onPackageAdded(String packageName) {
+            final PackageInfo app = getPackageInfo(packageName);
+            if (app == null) {
+                Slog.wtf(TAG, "Failed to get information of installed package: " + packageName);
+                return;
+            }
+            int uid = (app.applicationInfo != null) ? app.applicationInfo.uid : INVALID_UID;
+            if (uid == INVALID_UID) {
+                Slog.wtf(TAG, "Failed to get the uid of installed package: " + packageName
+                        + "uid: " + uid);
+                return;
+            }
+            if (app.requestedPermissions == null) {
+                return;
+            }
+            sendPackagePermissionsForUid(uid,
+                    filterPermission(Arrays.asList(app.requestedPermissions)));
+            synchronized (mPackageNameUidMap) {
+                mPackageNameUidMap.put(packageName, uid);
+            }
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName) {
+            int uid;
+            synchronized (mPackageNameUidMap) {
+                if (!mPackageNameUidMap.containsKey(packageName)) {
+                    return;
+                }
+                uid = mPackageNameUidMap.get(packageName);
+                mPackageNameUidMap.remove(packageName);
+            }
+            int permission = 0;
+            String[] packages = mPackageManager.getPackagesForUid(uid);
+            if (packages != null && packages.length > 0) {
+                for (String name : packages) {
+                    final PackageInfo app = getPackageInfo(name);
+                    if (app != null && app.requestedPermissions != null) {
+                        permission |= filterPermission(Arrays.asList(app.requestedPermissions));
+                    }
+                }
+            }
+            sendPackagePermissionsForUid(uid, permission);
+        }
+    }
+
     public PermissionMonitor(Context context, INetworkManagementService netd) {
         mContext = context;
         mPackageManager = context.getPackageManager();
@@ -87,12 +149,21 @@
     public synchronized void startMonitoring() {
         log("Monitoring");
 
-        List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS);
+        PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+        if (pmi != null) {
+            pmi.getPackageList(new PackageListObserver());
+        } else {
+            loge("failed to get the PackageManagerInternal service");
+        }
+        List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS
+                | MATCH_ANY_USER);
         if (apps == null) {
             loge("No apps");
             return;
         }
 
+        SparseIntArray netdPermsUids = new SparseIntArray();
+
         for (PackageInfo app : apps) {
             int uid = app.applicationInfo != null ? app.applicationInfo.uid : INVALID_UID;
             if (uid < 0) {
@@ -110,6 +181,17 @@
                     mApps.put(uid, hasRestrictedPermission);
                 }
             }
+
+            //TODO: unify the management of the permissions into one codepath.
+            if (app.requestedPermissions != null) {
+                int otherNetdPerms = filterPermission(Arrays.asList(app.requestedPermissions));
+                if (otherNetdPerms != 0) {
+                    netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms);
+                    synchronized (mPackageNameUidMap) {
+                        mPackageNameUidMap.put(app.applicationInfo.packageName, uid);
+                    }
+                }
+            }
         }
 
         List<UserInfo> users = mUserManager.getUsers(true);  // exclude dying users
@@ -121,6 +203,7 @@
 
         log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
         update(mUsers, mApps, true);
+        sendPackagePermissionsToNetd(netdPermsUids);
     }
 
     @VisibleForTesting
@@ -339,6 +422,107 @@
         }
     }
 
+    private static int filterPermission(List<String> requestedPermissions) {
+        int permissions = 0;
+        if (requestedPermissions.contains(INTERNET)) {
+            permissions |= INetd.PERMISSION_INTERNET;
+        }
+        if (requestedPermissions.contains(UPDATE_DEVICE_STATS)) {
+            permissions |= INetd.PERMISSION_UPDATE_DEVICE_STATS;
+        }
+        return permissions;
+    }
+
+    private PackageInfo getPackageInfo(String packageName) {
+        try {
+            PackageInfo app = mPackageManager.getPackageInfo(packageName, GET_PERMISSIONS
+                    | MATCH_ANY_USER);
+            return app;
+        } catch (NameNotFoundException e) {
+            // App not found.
+            loge("NameNotFoundException " + packageName);
+            return null;
+        }
+    }
+
+    /**
+     * Called by PackageListObserver when a package is installed/uninstalled. Send the updated
+     * permission information to netd.
+     *
+     * @param uid the app uid of the package installed
+     * @param permissions the permissions the app requested and netd cares about.
+     *
+     * @hide
+     */
+    private void sendPackagePermissionsForUid(int uid, int permissions) {
+        SparseIntArray netdPermissionsAppIds = new SparseIntArray();
+        netdPermissionsAppIds.put(uid, permissions);
+        sendPackagePermissionsToNetd(netdPermissionsAppIds);
+    }
+
+    /**
+     * Called by packageManagerService to send IPC to netd. Grant or revoke the INTERNET
+     * and/or UPDATE_DEVICE_STATS permission of the uids in array.
+     *
+     * @param netdPermissionsAppIds integer pairs of uids and the permission granted to it. If the
+     * permission is 0, revoke all permissions of that uid.
+     *
+     * @hide
+     */
+    private void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
+        INetd netdService = NetdService.getInstance();
+        if (netdService == null) {
+            Log.e(TAG, "Failed to get the netd service");
+            return;
+        }
+        ArrayList<Integer> allPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> internetPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> uninstalledAppIds = new ArrayList<>();
+        for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
+            int permissions = netdPermissionsAppIds.valueAt(i);
+            switch(permissions) {
+                case (INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS):
+                    allPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_INTERNET:
+                    internetPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.PERMISSION_UPDATE_DEVICE_STATS:
+                    updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                case INetd.NO_PERMISSIONS:
+                    uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    break;
+                default:
+                    Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
+                            + netdPermissionsAppIds.keyAt(i));
+            }
+        }
+        try {
+            // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
+            if (allPermissionAppIds.size() != 0) {
+                netdService.trafficSetNetPermForUids(
+                        INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                        ArrayUtils.convertToIntArray(allPermissionAppIds));
+            }
+            if (internetPermissionAppIds.size() != 0) {
+                netdService.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
+                        ArrayUtils.convertToIntArray(internetPermissionAppIds));
+            }
+            if (updateStatsPermissionAppIds.size() != 0) {
+                netdService.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                        ArrayUtils.convertToIntArray(updateStatsPermissionAppIds));
+            }
+            if (uninstalledAppIds.size() != 0) {
+                netdService.trafficSetNetPermForUids(INetd.NO_PERMISSIONS,
+                        ArrayUtils.convertToIntArray(uninstalledAppIds));
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Pass appId list of special permission failed." + e);
+        }
+    }
+
     private static void log(String s) {
         if (DBG) {
             Log.d(TAG, s);