Initial app filtering logic

This change adds support for filtering apps from one another based on
the declared <queries> tags they have in their manifest. By default,
enforcement of the checks will be bypassed via app op.

Test: atest AppsFilterTest
Test: visibility limited after `adb shell appops set --uid <pkg> QUERY_ALL_PACKAGES ignore
Test: blocking logged after `adb shell appops set --uid <pkg> QUERY_ALL_PACKAGES deny
Bug: 136675067
Change-Id: I7b0c3714298aac183dfe2ae9e94097587648d59f
diff --git a/api/current.txt b/api/current.txt
index 5d23b8f..919434e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -103,6 +103,7 @@
     field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
     field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
     field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+    field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index fb72e65..d20cc41 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -834,9 +834,12 @@
     public static final int OP_ACCESS_ACCESSIBILITY = 88;
     /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
     public static final int OP_READ_DEVICE_IDENTIFIERS = 89;
+    /** @hide Query all apps on device, regardless of declarations in the calling app manifest */
+    public static final int OP_QUERY_ALL_PACKAGES = 90;
+
     /** @hide */
     @UnsupportedAppUsage
-    public static final int _NUM_OP = 90;
+    public static final int _NUM_OP = 91;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1112,6 +1115,8 @@
     public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
     /** @hide Read device identifiers */
     public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
+    /** @hide Query all packages on device */
+    public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
 
     // Warning: If an permission is added here it also has to be added to
     // com.android.packageinstaller.permission.utils.EventLogger
@@ -1273,6 +1278,7 @@
             OP_LEGACY_STORAGE,                  // LEGACY_STORAGE
             OP_ACCESS_ACCESSIBILITY,            // ACCESS_ACCESSIBILITY
             OP_READ_DEVICE_IDENTIFIERS,         // READ_DEVICE_IDENTIFIERS
+            OP_QUERY_ALL_PACKAGES,              // QUERY_ALL_PACKAGES
     };
 
     /**
@@ -1369,6 +1375,7 @@
             OPSTR_LEGACY_STORAGE,
             OPSTR_ACCESS_ACCESSIBILITY,
             OPSTR_READ_DEVICE_IDENTIFIERS,
+            OPSTR_QUERY_ALL_PACKAGES,
     };
 
     /**
@@ -1466,6 +1473,7 @@
             "LEGACY_STORAGE",
             "ACCESS_ACCESSIBILITY",
             "READ_DEVICE_IDENTIFIERS",
+            "QUERY_ALL_PACKAGES",
     };
 
     /**
@@ -1564,6 +1572,7 @@
             null, // no permission for OP_LEGACY_STORAGE
             null, // no permission for OP_ACCESS_ACCESSIBILITY
             null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
+            null, // no permission for OP_QUERY_ALL_PACKAGES
     };
 
     /**
@@ -1662,6 +1671,7 @@
             null, // LEGACY_STORAGE
             null, // ACCESS_ACCESSIBILITY
             null, // READ_DEVICE_IDENTIFIERS
+            null, // QUERY_ALL_PACKAGES
     };
 
     /**
@@ -1759,6 +1769,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // QUERY_ALL_PACKAGES
     };
 
     /**
@@ -1855,6 +1866,7 @@
             AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
             AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
             AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
+            AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
     };
 
     /**
@@ -1955,6 +1967,7 @@
             false, // LEGACY_STORAGE
             false, // ACCESS_ACCESSIBILITY
             false, // READ_DEVICE_IDENTIFIERS
+            false, // QUERY_ALL_PACKAGES
     };
 
     /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index aa440d3..6a20484 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4599,6 +4599,11 @@
     <permission android:name="android.permission.MONITOR_INPUT"
                 android:protectionLevel="signature" />
 
+    <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
+    <permission android:name="android.permission.QUERY_ALL_PACKAGES"
+                android:protectionLevel="normal" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dce4bf3..b34c422 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1700,11 +1700,16 @@
         <!-- Add packages here -->
     </string-array>
 
+    <!-- The set of system packages on device that are queryable regardless of the contents of their
+         manifest. -->
     <string-array name="config_forceQueryablePackages" translatable="false">
         <item>com.android.settings</item>
         <!-- Add packages here -->
     </string-array>
 
+    <!-- If true, will force all packages on any system partition as queryable regardless of the
+         contents of their manifest. -->
+    <bool name="config_forceSystemPackagesQueryable">false</bool>
 
     <!-- Component name of the default wallpaper. This will be ImageWallpaper if not
          specified -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 123deeb..f7eb360 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -787,6 +787,7 @@
   <java-symbol type="string" name="emergency_calls_only" />
   <java-symbol type="array" name="config_ephemeralResolverPackage" />
   <java-symbol type="array" name="config_forceQueryablePackages" />
+  <java-symbol type="bool" name="config_forceSystemPackagesQueryable" />
   <java-symbol type="string" name="eventTypeAnniversary" />
   <java-symbol type="string" name="eventTypeBirthday" />
   <java-symbol type="string" name="eventTypeCustom" />
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
new file mode 100644
index 0000000..ab8cc53
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.os.Build;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.permission.IPermissionManager;
+import android.provider.DeviceConfig;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The entity responsible for filtering visibility between apps based on declarations in their
+ * manifests.
+ */
+class AppsFilter {
+
+    private static final String TAG = PackageManagerService.TAG;
+    /**
+     * This contains a list of packages that are implicitly queryable because another app explicitly
+     * interacted with it. For example, if application A starts a service in application B,
+     * application B is implicitly allowed to query for application A; regardless of any manifest
+     * entries.
+     */
+    private final SparseArray<HashMap<String, ArrayList<String>>> mImplicitlyQueryable =
+            new SparseArray<>();
+
+    /**
+     * A mapping from the set of packages that query other packages via package name to the
+     * list of packages that they can see.
+     */
+    private final HashMap<String, List<String>> mQueriesViaPackage = new HashMap<>();
+
+    /**
+     * A mapping from the set of packages that query others via intent to the list
+     * of packages that the intents resolve to.
+     */
+    private final HashMap<String, List<String>> mQueriesViaIntent = new HashMap<>();
+
+    /**
+     * A set of packages that are always queryable by any package, regardless of their manifest
+     * content.
+     */
+    private final HashSet<String> mForceQueryable;
+    /**
+     * A set of packages that are always queryable by any package, regardless of their manifest
+     * content.
+     */
+    private final Set<String> mForceQueryableByDevice;
+
+    /** True if all system apps should be made queryable by default. */
+    private final boolean mSystemAppsQueryable;
+
+    private final IPermissionManager mPermissionManager;
+
+    private final AppOpsManager mAppOpsManager;
+    private final ConfigProvider mConfigProvider;
+
+    AppsFilter(ConfigProvider configProvider, IPermissionManager permissionManager,
+            AppOpsManager appOpsManager, String[] forceQueryableWhitelist,
+            boolean systemAppsQueryable) {
+        mConfigProvider = configProvider;
+        mAppOpsManager = appOpsManager;
+        final HashSet<String> forceQueryableByDeviceSet = new HashSet<>();
+        Collections.addAll(forceQueryableByDeviceSet, forceQueryableWhitelist);
+        this.mForceQueryableByDevice = Collections.unmodifiableSet(forceQueryableByDeviceSet);
+        this.mForceQueryable = new HashSet<>();
+        mPermissionManager = permissionManager;
+        mSystemAppsQueryable = systemAppsQueryable;
+    }
+
+    public static AppsFilter create(Context context) {
+        final boolean forceSystemAppsQueryable =
+                context.getResources().getBoolean(R.bool.config_forceSystemPackagesQueryable);
+        final ConfigProvider configProvider = () -> DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE,
+                "package_query_filtering_enabled",
+                false);
+        final String[] forcedQueryablePackageNames;
+        if (forceSystemAppsQueryable) {
+            // all system apps already queryable, no need to read and parse individual exceptions
+            forcedQueryablePackageNames = new String[]{};
+        } else {
+            forcedQueryablePackageNames =
+                    context.getResources().getStringArray(R.array.config_forceQueryablePackages);
+            for (int i = 0; i < forcedQueryablePackageNames.length; i++) {
+                forcedQueryablePackageNames[i] = forcedQueryablePackageNames[i].intern();
+            }
+        }
+        IPermissionManager permissionmgr =
+                (IPermissionManager) ServiceManager.getService("permissionmgr");
+        return new AppsFilter(configProvider, permissionmgr,
+                context.getSystemService(AppOpsManager.class), forcedQueryablePackageNames,
+                forceSystemAppsQueryable);
+    }
+
+    /** Returns true if the querying package may query for the potential target package */
+    private static boolean canQuery(PackageParser.Package querying,
+            PackageParser.Package potentialTarget) {
+        if (querying.mQueriesIntents == null) {
+            return false;
+        }
+        for (Intent intent : querying.mQueriesIntents) {
+            for (PackageParser.Activity activity : potentialTarget.activities) {
+                if (activity.intents != null) {
+                    for (PackageParser.ActivityIntentInfo filter : activity.intents) {
+                        if (matches(intent, filter)) {
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /** Returns true if the given intent matches the given filter. */
+    private static boolean matches(Intent intent, PackageParser.ActivityIntentInfo filter) {
+        return filter.match(intent.getAction(), intent.getType(), intent.getScheme(),
+                intent.getData(), intent.getCategories(), "AppsFilter") > 0;
+    }
+
+    /**
+     * Marks that a package initiated an interaction with another package, granting visibility of
+     * the prior from the former.
+     *
+     * @param initiatingPackage the package initiating the interaction
+     * @param targetPackage     the package being interacted with and thus gaining visibility of the
+     *                          initiating package.
+     * @param userId            the user in which this interaction was taking place
+     */
+    private void markAppInteraction(
+            PackageSetting initiatingPackage, PackageSetting targetPackage, int userId) {
+        HashMap<String, ArrayList<String>> currentUser = mImplicitlyQueryable.get(userId);
+        if (currentUser == null) {
+            currentUser = new HashMap<>();
+            mImplicitlyQueryable.put(userId, currentUser);
+        }
+        if (!currentUser.containsKey(targetPackage.pkg.packageName)) {
+            currentUser.put(targetPackage.pkg.packageName, new ArrayList<>());
+        }
+        currentUser.get(targetPackage.pkg.packageName).add(initiatingPackage.pkg.packageName);
+    }
+
+    /**
+     * Adds a package that should be considered when filtering visibility between apps.
+     *
+     * @param newPkg   the new package being added
+     * @param existing all other packages currently on the device.
+     */
+    public void addPackage(PackageParser.Package newPkg,
+            Map<String, PackageParser.Package> existing) {
+        // let's re-evaluate the ability of already added packages to see this new package
+        if (newPkg.mForceQueryable
+                || (mSystemAppsQueryable && (newPkg.isSystem() || newPkg.isUpdatedSystemApp()))) {
+            mForceQueryable.add(newPkg.packageName);
+        } else {
+            for (String packageName : mQueriesViaIntent.keySet()) {
+                if (packageName == newPkg.packageName) {
+                    continue;
+                }
+                final PackageParser.Package existingPackage = existing.get(packageName);
+                if (canQuery(existingPackage, newPkg)) {
+                    mQueriesViaIntent.get(packageName).add(newPkg.packageName);
+                }
+            }
+        }
+        // if the new package declares them, let's evaluate its ability to see existing packages
+        mQueriesViaIntent.put(newPkg.packageName, new ArrayList<>());
+        for (PackageParser.Package existingPackage : existing.values()) {
+            if (existingPackage.packageName == newPkg.packageName) {
+                continue;
+            }
+            if (existingPackage.mForceQueryable
+                    || (mSystemAppsQueryable
+                    && (newPkg.isSystem() || newPkg.isUpdatedSystemApp()))) {
+                continue;
+            }
+            if (canQuery(newPkg, existingPackage)) {
+                mQueriesViaIntent.get(newPkg.packageName).add(existingPackage.packageName);
+            }
+        }
+        final ArrayList<String> queriesPackages = new ArrayList<>(
+                newPkg.mQueriesPackages == null ? 0 : newPkg.mQueriesPackages.size());
+        if (newPkg.mQueriesPackages != null) {
+            queriesPackages.addAll(newPkg.mQueriesPackages);
+        }
+        mQueriesViaPackage.put(newPkg.packageName, queriesPackages);
+    }
+
+    /**
+     * Removes a package for consideration when filtering visibility between apps.
+     *
+     * @param packageName the name of the package being removed.
+     */
+    public void removePackage(String packageName) {
+        mForceQueryable.remove(packageName);
+
+        for (int i = 0; i < mImplicitlyQueryable.size(); i++) {
+            mImplicitlyQueryable.valueAt(i).remove(packageName);
+            for (ArrayList<String> initiators : mImplicitlyQueryable.valueAt(i).values()) {
+                initiators.remove(packageName);
+            }
+        }
+
+        mQueriesViaIntent.remove(packageName);
+        for (List<String> declarators : mQueriesViaIntent.values()) {
+            declarators.remove(packageName);
+        }
+
+        mQueriesViaPackage.remove(packageName);
+    }
+
+    /**
+     * Returns true if the calling package should not be able to see the target package, false if no
+     * filtering should be done.
+     *
+     * @param callingUid       the uid of the caller attempting to access a package
+     * @param callingSetting   the setting attempting to access a package or null if it could not be
+     *                         found
+     * @param targetPkgSetting the package being accessed
+     * @param userId           the user in which this access is being attempted
+     */
+    public boolean shouldFilterApplication(int callingUid, @Nullable SettingBase callingSetting,
+            PackageSetting targetPkgSetting, int userId) {
+        if (callingUid < Process.FIRST_APPLICATION_UID) {
+            return false;
+        }
+        if (callingSetting == null) {
+            Slog.wtf(TAG, "No setting found for non system uid " + callingUid);
+            return true;
+        }
+        PackageSetting callingPkgSetting = null;
+        if (callingSetting instanceof PackageSetting) {
+            callingPkgSetting = (PackageSetting) callingSetting;
+            if (!shouldFilterApplicationInternal(callingPkgSetting, targetPkgSetting,
+                    userId)) {
+                // TODO: actually base this on a start / launch (not just a query)
+                markAppInteraction(callingPkgSetting, targetPkgSetting, userId);
+                return false;
+            }
+        } else if (callingSetting instanceof SharedUserSetting) {
+            final ArraySet<PackageSetting> packageSettings =
+                    ((SharedUserSetting) callingSetting).packages;
+            if (packageSettings != null && packageSettings.size() > 0) {
+                for (PackageSetting packageSetting : packageSettings) {
+                    if (!shouldFilterApplicationInternal(packageSetting, targetPkgSetting,
+                            userId)) {
+                        // TODO: actually base this on a start / launch (not just a query)
+                        markAppInteraction(packageSetting, targetPkgSetting, userId);
+                        return false;
+                    }
+                    if (callingPkgSetting == null && packageSetting.pkg != null) {
+                        callingPkgSetting = packageSetting;
+                    }
+                }
+            } else {
+                return true;
+            }
+        }
+        if (callingPkgSetting == null) {
+            Slog.wtf(TAG, "What... " + callingSetting);
+            return true;
+        }
+        final int mode = mAppOpsManager
+                .checkOpNoThrow(AppOpsManager.OP_QUERY_ALL_PACKAGES, callingUid,
+                        callingPkgSetting.pkg.packageName);
+        switch (mode) {
+            case AppOpsManager.MODE_DEFAULT:
+                // if default, let's rely on remote feature toggle to determine whether to
+                // actually filter
+                return mConfigProvider.isEnabled();
+            case AppOpsManager.MODE_ALLOWED:
+                // explicitly allowed to see all packages, don't filter
+                return false;
+            case AppOpsManager.MODE_ERRORED:
+                // deny / error: let's log so developer can audit usages
+                Slog.i(TAG, callingPkgSetting.pkg.packageName
+                        + " blocked from accessing " + targetPkgSetting.pkg.packageName);
+            case AppOpsManager.MODE_IGNORED:
+                // fall through
+            default:
+                return true;
+        }
+    }
+
+    private boolean shouldFilterApplicationInternal(
+            PackageSetting callingPkgSetting, PackageSetting targetPkgSetting, int userId) {
+        final String callingName = callingPkgSetting.pkg.packageName;
+        final String targetName = targetPkgSetting.pkg.packageName;
+        if (callingPkgSetting.pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.R) {
+            return false;
+        }
+        if (isImplicitlyQueryableSystemApp(targetPkgSetting)) {
+            return false;
+        }
+        if (targetPkgSetting.pkg.mForceQueryable) {
+            return false;
+        }
+        if (mForceQueryable.contains(targetName)) {
+            return false;
+        }
+        if (mQueriesViaPackage.containsKey(callingName)
+                && mQueriesViaPackage.get(callingName).contains(
+                targetName)) {
+            // the calling package has explicitly declared the target package; allow
+            return false;
+        } else if (mQueriesViaIntent.containsKey(callingName)
+                && mQueriesViaIntent.get(callingName).contains(targetName)) {
+            return false;
+        }
+        if (mImplicitlyQueryable.get(userId) != null
+                && mImplicitlyQueryable.get(userId).containsKey(callingName)
+                && mImplicitlyQueryable.get(userId).get(callingName).contains(targetName)) {
+            return false;
+        }
+        try {
+            if (mPermissionManager.checkPermission(
+                    Manifest.permission.QUERY_ALL_PACKAGES, callingName, userId)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        } catch (RemoteException e) {
+            return true;
+        }
+        return true;
+    }
+
+    private boolean isImplicitlyQueryableSystemApp(PackageSetting targetPkgSetting) {
+        return targetPkgSetting.isSystem() && (mSystemAppsQueryable
+                || mForceQueryableByDevice.contains(targetPkgSetting.pkg.packageName));
+    }
+
+    public interface ConfigProvider {
+        boolean isEnabled();
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ebbb193..7c10ec7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -768,6 +768,8 @@
         }
     }
 
+    private final AppsFilter mAppsFilter;
+
     class PackageParserCallback implements PackageParser.Callback {
         @Override public final boolean hasFeature(String feature) {
             return PackageManagerService.this.hasSystemFeature(feature, 0);
@@ -2426,6 +2428,8 @@
         mProtectedPackages = new ProtectedPackages(mContext);
 
         mApexManager = ApexManager.create(context);
+        mAppsFilter = AppsFilter.create(context);
+
         // CHECKSTYLE:OFF IndentationCheck
         synchronized (mInstallLock) {
         // writer
@@ -4332,7 +4336,9 @@
             return !mInstantAppRegistry.isInstantAccessGranted(
                     userId, UserHandle.getAppId(callingUid), ps.appId);
         }
-        return false;
+        int appId = UserHandle.getAppId(callingUid);
+        final SettingBase callingPs = mSettings.getSettingLPr(appId);
+        return mAppsFilter.shouldFilterApplication(callingUid, callingPs, ps, userId);
     }
 
     /**
@@ -11673,6 +11679,7 @@
             ksms.addScannedPackageLPw(pkg);
 
             mComponentResolver.addAllComponents(pkg, chatty);
+            mAppsFilter.addPackage(pkg, mPackages);
 
             // Don't allow ephemeral applications to define new permissions groups.
             if ((scanFlags & SCAN_AS_INSTANT_APP) != 0) {
@@ -11883,7 +11890,7 @@
 
     void cleanPackageDataStructuresLILPw(PackageParser.Package pkg, boolean chatty) {
         mComponentResolver.removeAllComponents(pkg, chatty);
-
+        mAppsFilter.removePackage(pkg.packageName);
         mPermissionManager.removeAllPermissions(pkg, chatty);
 
         final int instrumentationSize = pkg.instrumentation.size();
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
new file mode 100644
index 0000000..65f9e32
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.os.Build;
+import android.os.Process;
+import android.permission.IPermissionManager;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class AppsFilterTest {
+
+    private static final int DUMMY_CALLING_UID = 10345;
+
+    @Mock
+    IPermissionManager mPermissionManagerMock;
+
+    @Mock
+    AppsFilter.ConfigProvider mConfigProviderMock;
+
+    @Mock
+    AppOpsManager mAppOpsManager;
+
+    private Map<String, PackageParser.Package> mExisting = new ArrayMap<>();
+
+    private static PackageBuilder pkg(String packageName) {
+        return new PackageBuilder(packageName)
+                .setApplicationInfoTargetSdkVersion(Build.VERSION_CODES.R);
+    }
+
+    private static PackageBuilder pkg(String packageName, Intent... queries) {
+        return pkg(packageName).setQueriesIntents(queries);
+    }
+
+    private static PackageBuilder pkg(String packageName, String... queriesPackages) {
+        return pkg(packageName).setQueriesPackages(queriesPackages);
+    }
+
+    private static PackageBuilder pkg(String packageName, IntentFilter... filters) {
+        final PackageBuilder packageBuilder = pkg(packageName).addActivity(
+                pkg -> new PackageParser.ParseComponentArgs(pkg, new String[1], 0, 0, 0, 0, 0, 0,
+                        new String[]{packageName}, 0, 0, 0), new ActivityInfo());
+        for (IntentFilter filter : filters) {
+            packageBuilder.addActivityIntentInfo(0 /* index */, activity -> {
+                final PackageParser.ActivityIntentInfo info =
+                        new PackageParser.ActivityIntentInfo(activity);
+                if (filter.countActions() > 0) {
+                    filter.actionsIterator().forEachRemaining(info::addAction);
+                }
+                if (filter.countCategories() > 0) {
+                    filter.actionsIterator().forEachRemaining(info::addAction);
+                }
+                if (filter.countDataAuthorities() > 0) {
+                    filter.authoritiesIterator().forEachRemaining(info::addDataAuthority);
+                }
+                if (filter.countDataSchemes() > 0) {
+                    filter.schemesIterator().forEachRemaining(info::addDataScheme);
+                }
+                return info;
+            });
+        }
+        return packageBuilder;
+    }
+
+    @Before
+    public void setup() throws Exception {
+        mExisting = new ArrayMap<>();
+
+        MockitoAnnotations.initMocks(this);
+        when(mPermissionManagerMock
+                .checkPermission(anyString(), anyString(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mConfigProviderMock.isEnabled()).thenReturn(true);
+        when(mAppOpsManager.checkOpNoThrow(eq(AppOpsManager.OP_QUERY_ALL_PACKAGES), eq(
+                DUMMY_CALLING_UID), anyString())).thenReturn(AppOpsManager.MODE_DEFAULT);
+    }
+
+    @Test
+    public void testQueriesAction_FilterMatches() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package", new IntentFilter("TEST_ACTION"))).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package", new Intent("TEST_ACTION"))).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testQueriesAction_NoMatchingAction_Filters() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter,
+                pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package", new Intent("TEST_ACTION"))).build();
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testQueriesAction_NoMatchingActionFilterLowSdk_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter, pkg("com.some.other.package",
+                new Intent("TEST_ACTION")).setApplicationInfoTargetSdkVersion(
+                Build.VERSION_CODES.P)).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testNoQueries_Filters() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testForceQueryable_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target =
+                simulateAddPackage(appsFilter, pkg("com.some.package").setForceQueryable(true))
+                        .build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testForceQueryableByDevice_SystemCaller_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{"com.some.package"}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"))
+                .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
+                .build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testForceQueryableByDevice_NonSystemCaller_Filters() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{"com.some.package"}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+
+    @Test
+    public void testSystemQueryable_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, true /* system force queryable */);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package"))
+                .setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
+                .build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testQueriesPackage_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package", "com.some.package")).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testNoQueries_AppOpModeDeny_Filters() {
+        when(mAppOpsManager.checkOpNoThrow(eq(AppOpsManager.OP_QUERY_ALL_PACKAGES), eq(
+                DUMMY_CALLING_UID), anyString())).thenReturn(AppOpsManager.MODE_ERRORED);
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testNoQueries_AppOpModeAllow_DoesntFilter() {
+        when(mAppOpsManager.checkOpNoThrow(eq(AppOpsManager.OP_QUERY_ALL_PACKAGES), eq(
+                DUMMY_CALLING_UID), anyString())).thenReturn(AppOpsManager.MODE_ALLOWED);
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testNoQueries_AppOpModeIgnore_Filters() {
+        when(mAppOpsManager.checkOpNoThrow(eq(AppOpsManager.OP_QUERY_ALL_PACKAGES), eq(
+                DUMMY_CALLING_UID), anyString())).thenReturn(AppOpsManager.MODE_IGNORED);
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testNoQueries_FeatureOff_DoesntFilter() {
+        when(mConfigProviderMock.isEnabled()).thenReturn(false);
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+        PackageSetting calling = simulateAddPackage(appsFilter,
+                pkg("com.some.other.package")).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+    }
+
+    @Test
+    public void testSystemUid_DoesntFilter() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+
+        assertFalse(appsFilter.shouldFilterApplication(0, null, target, 0));
+        assertFalse(appsFilter.shouldFilterApplication(
+                Process.FIRST_APPLICATION_UID - 1, null, target, 0));
+    }
+
+    @Test
+    public void testNonSystemUid_NoCallingSetting_Filters() {
+        final AppsFilter appsFilter =
+                new AppsFilter(mConfigProviderMock, mPermissionManagerMock, mAppOpsManager,
+                        new String[]{}, false);
+
+        PackageSetting target = simulateAddPackage(appsFilter, pkg("com.some.package")).build();
+
+        assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, null, target, 0));
+    }
+
+    private PackageSettingBuilder simulateAddPackage(AppsFilter filter,
+            PackageBuilder newPkgBuilder) {
+        PackageParser.Package newPkg = newPkgBuilder.build();
+        filter.addPackage(newPkg, mExisting);
+        mExisting.put(newPkg.packageName, newPkg);
+        return new PackageSettingBuilder()
+                .setPackage(newPkg)
+                .setName(newPkg.packageName)
+                .setCodePath("/")
+                .setResourcePath("/")
+                .setPVersionCode(1L);
+    }
+
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageBuilder.java
index 470d4fa..c38672c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageBuilder.java
@@ -16,10 +16,16 @@
 
 package com.android.server.pm;
 
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageParser;
 
 import com.android.internal.util.ArrayUtils;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
 class PackageBuilder {
     final PackageParser.Package mPkg;
 
@@ -119,4 +125,52 @@
         mPkg.applicationInfo.flags |= flag;
         return this;
     }
+
+    public PackageBuilder setApplicationInfoTargetSdkVersion(int versionCode) {
+        mPkg.applicationInfo.targetSdkVersion = versionCode;
+        return this;
+    }
+
+    public PackageBuilder setQueriesIntents(Collection<Intent> queriesIntents) {
+        mPkg.mQueriesIntents = new ArrayList<>(queriesIntents);
+        return this;
+    }
+
+    public PackageBuilder setQueriesIntents(Intent... intents) {
+        return setQueriesIntents(Arrays.asList(intents));
+    }
+
+    public PackageBuilder setQueriesPackages(Collection<String> queriesPackages) {
+        mPkg.mQueriesPackages = new ArrayList<>(queriesPackages);
+        return this;
+    }
+
+    public PackageBuilder setQueriesPackages(String... queriesPackages) {
+        return setQueriesPackages(Arrays.asList(queriesPackages));
+    }
+
+    public PackageBuilder setForceQueryable(boolean forceQueryable) {
+        mPkg.mForceQueryable = forceQueryable;
+        return this;
+    }
+
+    public interface ParseComponentArgsCreator {
+        PackageParser.ParseComponentArgs create(PackageParser.Package pkg);
+    }
+
+    public PackageBuilder addActivity(ParseComponentArgsCreator argsCreator, ActivityInfo info) {
+        mPkg.activities.add(new PackageParser.Activity(argsCreator.create(mPkg), info));
+        return this;
+    }
+
+    public interface ActivityIntentInfoCreator {
+        PackageParser.ActivityIntentInfo create(PackageParser.Activity activity);
+    }
+
+    public PackageBuilder addActivityIntentInfo(
+            int activityIndex, ActivityIntentInfoCreator creator) {
+        final PackageParser.Activity activity = mPkg.activities.get(activityIndex);
+        activity.intents.add(creator.create(activity));
+        return this;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
index b42cfd8..06c6314 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageSettingBuilder.java
@@ -16,6 +16,7 @@
 
 package com.android.server.pm;
 
+import android.content.pm.PackageParser;
 import android.content.pm.PackageUserState;
 import android.util.SparseArray;
 
@@ -41,6 +42,12 @@
     private long[] mUsesStaticLibrariesVersions;
     private String mVolumeUuid;
     private SparseArray<PackageUserState> mUserStates = new SparseArray<>();
+    private PackageParser.Package mPkg;
+
+    public PackageSettingBuilder setPackage(PackageParser.Package pkg) {
+        this.mPkg = pkg;
+        return this;
+    }
 
     public PackageSettingBuilder setName(String name) {
         this.mName = name;
@@ -144,6 +151,7 @@
                 mCpuAbiOverrideString, mPVersionCode, mPkgFlags, mPrivateFlags, mParentPackageName,
                 mChildPackageNames, mSharedUserId, mUsesStaticLibraries,
                 mUsesStaticLibrariesVersions);
+        packageSetting.pkg = mPkg;
         packageSetting.volumeUuid = this.mVolumeUuid;
         for (int i = 0; i < mUserStates.size(); i++) {
             packageSetting.setUserState(mUserStates.keyAt(i), mUserStates.valueAt(i));