Merge "Add missing ServiceNotFoundException class"
diff --git a/api/current.txt b/api/current.txt
index 33564a2..e42913c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6007,6 +6007,7 @@
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
+    method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
     method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 150d925..873f021 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6179,6 +6179,7 @@
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
+    method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
     method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
diff --git a/api/test-current.txt b/api/test-current.txt
index 0730cc4..4f2bea5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6023,6 +6023,7 @@
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
+    method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
     method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ef1e55a..2fa2318 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6713,11 +6713,14 @@
     }
 
     /**
-     * Called by device owner/ profile owner in managed profile to bind the service with each other.
+     * Called by a device owner to bind to a service from a profile owner of a managed profile or
+     * vice versa. See {@link #getBindDeviceAdminTargetUsers} for a definition of which
+     * device/profile owners are allowed to bind to services of another profile/device owner.
+     * <p>
      * The service must be unexported. Note that the {@link Context} used to obtain this
      * {@link DevicePolicyManager} instance via {@link Context#getSystemService(Class)} will be used
      * to bind to the {@link android.app.Service}.
-     * STOPSHIP (b/31952368): Update the javadoc after we policy to control which packages can talk.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param serviceIntent Identifies the service to connect to.  The Intent must specify either an
      *        explicit component name or a package name to match an
@@ -6726,11 +6729,15 @@
      *        valid {@link ServiceConnection} object; it must not be {@code null}.
      * @param flags Operation options for the binding operation. See
      *        {@link Context#bindService(Intent, ServiceConnection, int)}.
-     * @param targetUser Which user to bind to.
+     * @param targetUser Which user to bind to. Must be one of the users returned by
+     *        {@link #getBindDeviceAdminTargetUsers}, otherwise a {@link SecurityException} will
+     *        be thrown.
      * @return If you have successfully bound to the service, {@code true} is returned;
      *         {@code false} is returned if the connection is not made and you will not
      *         receive the service object.
+     *
      * @see Context#bindService(Intent, ServiceConnection, int)
+     * @see #getBindDeviceAdminTargetUsers(ComponentName)
      */
     public boolean bindDeviceAdminServiceAsUser(
             @NonNull ComponentName admin,  Intent serviceIntent, @NonNull ServiceConnection conn,
@@ -6749,6 +6756,29 @@
     }
 
     /**
+     * Returns the list of target users that the calling device or profile owner can use when
+     * calling {@link #bindDeviceAdminServiceAsUser}.
+     * <p>
+     * A device owner can bind to a service from a profile owner of a managed profile and
+     * vice versa, provided that:
+     * <ul>
+     * <li>Both belong to the same package name.
+     * <li>The managed profile is a profile of the user where the device owner is set.
+     *     See {@link UserManager#getUserProfiles()}
+     * <li>Both users are affiliated.
+     *         STOPSHIP(b/32326223) Add reference to setAffiliationIds here once public.
+     * </ul>
+     */
+    public @NonNull List<UserHandle> getBindDeviceAdminTargetUsers(@NonNull ComponentName admin) {
+        throwIfParentInstance("getBindDeviceAdminTargetUsers");
+        try {
+            return mService.getBindDeviceAdminTargetUsers(admin);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Called by the system to get the time at which the device owner last retrieved security
      * logging entries.
      *
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d14e0d0..b7e0e92 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -325,6 +325,7 @@
     boolean bindDeviceAdminServiceAsUser(in ComponentName admin,
         IApplicationThread caller, IBinder token, in Intent service,
         IServiceConnection connection, int flags, int targetUserId);
+    List<UserHandle> getBindDeviceAdminTargetUsers(in ComponentName admin);
 
     long getLastSecurityLogRetrievalTime();
     long getLastBugReportRequestTime();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a8db535..bc28142 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8040,7 +8040,7 @@
 
         applyPolicy(pkg, policyFlags);
 
-        assertPackageIsValid(pkg, policyFlags);
+        assertPackageIsValid(pkg, policyFlags, scanFlags);
 
         // Initialize package source and resource directories
         final File scanFile = new File(pkg.codePath);
@@ -8486,7 +8486,7 @@
      *
      * @throws PackageManagerException If the package fails any of the validation checks
      */
-    private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags)
+    private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags, int scanFlags)
             throws PackageManagerException {
         if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
             assertCodePolicy(pkg);
@@ -8549,7 +8549,7 @@
             // a new system package, we allow the codepath to change from a system location
             // to the user-installed location. If we don't allow this change, any newer,
             // user-installed version of the application will be ignored.
-            if ((policyFlags & SCAN_REQUIRE_KNOWN) != 0) {
+            if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
                 if (mExpectingBetter.containsKey(pkg.packageName)) {
                     logCriticalInfo(Log.WARN,
                             "Relax SCAN_REQUIRE_KNOWN requirement for package " + pkg.packageName);
@@ -8578,7 +8578,7 @@
             // that conflict with existing packages.  Only do this if the
             // package isn't already installed, since we don't want to break
             // things that are installed.
-            if ((policyFlags & SCAN_NEW_INSTALL) != 0) {
+            if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
                 final int N = pkg.providers.size();
                 int i;
                 for (i=0; i<N; i++) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 29c9019..f7bb190 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6634,7 +6634,8 @@
     }
 
     private boolean isManagedProfile(int userHandle) {
-        return getUserInfo(userHandle).isManagedProfile();
+        final UserInfo user = getUserInfo(userHandle);
+        return user != null && user.isManagedProfile();
     }
 
     private void enableIfNecessary(String packageName, int userId) {
@@ -8903,9 +8904,7 @@
         synchronized (this) {
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         }
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
-        final UserInfo user = getUserInfo(callingUserId);
-        return user != null && user.isManagedProfile();
+        return isManagedProfile(mInjector.userHandleGetCallingUserId());
     }
 
     @Override
@@ -9537,65 +9536,97 @@
         Preconditions.checkNotNull(caller);
         Preconditions.checkNotNull(serviceIntent);
         Preconditions.checkNotNull(connection);
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
-        Preconditions.checkArgument(callingUserId != targetUserId,
+        Preconditions.checkArgument(mInjector.userHandleGetCallingUserId() != targetUserId,
                 "target user id must be different from the calling user id");
 
-        synchronized (this) {
-            final ActiveAdmin callingAdmin = getActiveAdminForCallerLocked(admin,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            // Ensure the target user is valid.
-            if (isDeviceOwner(callingAdmin)) {
-                enforceManagedProfile(targetUserId, "Target user must be a managed profile");
-            } else {
-                // Further lock down to profile owner in managed profile.
-                enforceManagedProfile(callingUserId,
-                        "Only support profile owner in managed profile.");
-                if (mOwners.getDeviceOwnerUserId() != targetUserId) {
-                    throw new SecurityException("Target user must be a device owner.");
-                }
-            }
+        if (!getBindDeviceAdminTargetUsers(admin).contains(UserHandle.of(targetUserId))) {
+            throw new SecurityException("Not allowed to bind to target user id");
         }
+
+        final String targetPackage;
+        synchronized (this) {
+            targetPackage = getOwnerPackageNameForUserLocked(targetUserId);
+        }
+
         final long callingIdentity = mInjector.binderClearCallingIdentity();
         try {
-            if (!mUserManager.isSameProfileGroup(callingUserId, targetUserId)) {
-                throw new SecurityException(
-                        "Can only bind service across users under the same profile group");
-            }
-            final String targetPackage;
-            synchronized (this) {
-                targetPackage = getOwnerPackageNameForUserLocked(targetUserId);
-            }
-            // STOPSHIP(b/31952368): Add policy to control which packages can talk.
-            if (TextUtils.isEmpty(targetPackage) || !targetPackage.equals(admin.getPackageName())) {
-                throw new SecurityException("Device owner and profile owner must be the same " +
-                        "package in order to communicate.");
-            }
             // Validate and sanitize the incoming service intent.
             final Intent sanitizedIntent =
-                    createCrossUserServiceIntent(serviceIntent, targetPackage);
+                    createCrossUserServiceIntent(serviceIntent, targetPackage, targetUserId);
             if (sanitizedIntent == null) {
                 // Fail, cannot lookup the target service.
                 throw new SecurityException("Invalid intent or failed to look up the service");
             }
+
             // Ask ActivityManager to bind it. Notice that we are binding the service with the
             // caller app instead of DevicePolicyManagerService.
-            try {
-                return mInjector.getIActivityManager().bindService(
-                        caller, activtiyToken, serviceIntent,
-                        serviceIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                        connection, flags, mContext.getOpPackageName(),
-                        targetUserId) != 0;
-            } catch (RemoteException ex) {
-                // Same process, should not happen.
-            }
+            return mInjector.getIActivityManager().bindService(
+                    caller, activtiyToken, serviceIntent,
+                    serviceIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    connection, flags, mContext.getOpPackageName(),
+                    targetUserId) != 0;
+        } catch (RemoteException ex) {
+            // Same process, should not happen.
         } finally {
             mInjector.binderRestoreCallingIdentity(callingIdentity);
         }
-        // Fail to bind.
+
+        // Failed to bind.
         return false;
     }
 
+    @Override
+    public @NonNull List<UserHandle> getBindDeviceAdminTargetUsers(@NonNull ComponentName admin) {
+        if (!mHasFeature) {
+            return Collections.emptyList();
+        }
+        Preconditions.checkNotNull(admin);
+        ArrayList<UserHandle> targetUsers = new ArrayList<>();
+
+        synchronized (this) {
+            ActiveAdmin callingOwner = getActiveAdminForCallerLocked(
+                    admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            final int callingUserId = mInjector.userHandleGetCallingUserId();
+            final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner);
+            final boolean isCallerManagedProfile = isManagedProfile(callingUserId);
+            if (!isCallerDeviceOwner && !isCallerManagedProfile
+                    /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public
+                    ||   !isAffiliatedUser(callingUserId) */) {
+                return targetUsers;
+            }
+
+            final long callingIdentity = mInjector.binderClearCallingIdentity();
+            try {
+                String callingOwnerPackage = callingOwner.info.getComponent().getPackageName();
+                for (int userId : mUserManager.getProfileIds(
+                        callingUserId, /* enabledOnly= */ false)) {
+                    if (userId == callingUserId) {
+                        continue;
+                    }
+
+                    // We only allow the device owner and a managed profile owner to bind to each
+                    // other.
+                    if ((isCallerManagedProfile && userId == mOwners.getDeviceOwnerUserId())
+                            || (isCallerDeviceOwner && isManagedProfile(userId))) {
+                        String targetOwnerPackage = getOwnerPackageNameForUserLocked(userId);
+
+                        // Both must be the same package and be affiliated in order to bind.
+                        if (callingOwnerPackage.equals(targetOwnerPackage)
+                            /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public
+                               && isAffiliatedUser(userId)*/) {
+                            targetUsers.add(UserHandle.of(userId));
+                        }
+                    }
+                }
+            } finally {
+                mInjector.binderRestoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        return targetUsers;
+    }
+
     /**
      * Return true if a given user has any accounts that'll prevent installing a device or profile
      * owner {@code owner}.
@@ -9776,7 +9807,7 @@
      * Return the package name of owner in a given user.
      */
     private String getOwnerPackageNameForUserLocked(int userId) {
-        return getDeviceOwnerUserId() == userId
+        return mOwners.getDeviceOwnerUserId() == userId
                 ? mOwners.getDeviceOwnerPackageName()
                 : mOwners.getProfileOwnerPackage(userId);
     }
@@ -9787,14 +9818,19 @@
      * @return Intent that have component explicitly set. {@code null} if the incoming intent
      *         or target service is invalid.
      */
-    private Intent createCrossUserServiceIntent (
-            @NonNull Intent rawIntent, @NonNull String expectedPackageName) {
+    private Intent createCrossUserServiceIntent(
+            @NonNull Intent rawIntent, @NonNull String expectedPackageName,
+            @UserIdInt int targetUserId) throws RemoteException {
         if (rawIntent.getComponent() == null && rawIntent.getPackage() == null) {
             Log.e(LOG_TAG, "Service intent must be explicit (with a package name or component): "
                     + rawIntent);
             return null;
         }
-        ResolveInfo info = mInjector.getPackageManager().resolveService(rawIntent, 0);
+        ResolveInfo info = mIPackageManager.resolveService(
+                rawIntent,
+                rawIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                0,  // flags
+                targetUserId);
         if (info == null || info.serviceInfo == null) {
             Log.e(LOG_TAG, "Fail to look up the service: " + rawIntent);
             return null;
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
index d81b4ba..d3f0f89 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
@@ -179,7 +179,7 @@
                 g.drawString("Actual", 2 * imageWidth + 10, 20);
             }
 
-            File output = new File(getTempDir(), "delta-" + imageName);
+            File output = new File(getFailureDir(), "delta-" + imageName);
             if (output.exists()) {
                 boolean deleted = output.delete();
                 assertTrue(deleted);
@@ -302,15 +302,16 @@
     }
 
     /**
-     * Temp directory where to write the thumbnails and deltas.
+     * Directory where to write the thumbnails and deltas.
      */
     @NonNull
-    private static File getTempDir() {
-        if (System.getProperty("os.name").equals("Mac OS X")) {
-            return new File("/tmp"); //$NON-NLS-1$
-        }
+    private static File getFailureDir() {
+        String workingDirString = System.getProperty("user.dir");
+        File failureDir = new File(workingDirString, "out/failures");
 
-        return new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
+        //noinspection ResultOfMethodCallIgnored
+        failureDir.mkdirs();
+        return failureDir; //$NON-NLS-1$
     }
 
     /**
@@ -319,7 +320,7 @@
     @NonNull
     private static String saveImageAndAppendMessage(@NonNull BufferedImage image,
             @NonNull String initialMessage, @NonNull String relativePath) throws IOException {
-        File output = new File(getTempDir(), getName(relativePath));
+        File output = new File(getFailureDir(), getName(relativePath));
         if (output.exists()) {
             boolean deleted = output.delete();
             assertTrue(deleted);