Shadow bindDeviceAdminServiceAsUser

PiperOrigin-RevId: 273978733
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
index ee2f8d2..d4cf81c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
@@ -23,23 +23,28 @@
 import static org.junit.Assert.fail;
 import static org.robolectric.Shadows.shadowOf;
 
+import android.app.Application;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.SystemUpdatePolicy;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
 import android.os.UserManager;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
@@ -50,6 +55,7 @@
 @RunWith(AndroidJUnit4.class)
 public final class ShadowDevicePolicyManagerTest {
 
+  private Application context;
   private DevicePolicyManager devicePolicyManager;
   private UserManager userManager;
   private ComponentName testComponent;
@@ -57,7 +63,7 @@
 
   @Before
   public void setUp() {
-    Context context = ApplicationProvider.getApplicationContext();
+    context = ApplicationProvider.getApplicationContext();
     devicePolicyManager =
         (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
 
@@ -1501,4 +1507,67 @@
 
     assertThat(devicePolicyManager.getSystemUpdatePolicy()).isEqualTo(policy);
   }
+
+  @Test
+  @Config(minSdk = O)
+  public void getBindDeviceAdminTargetUsers_returnsEmptyByDefault() {
+    assertThat(devicePolicyManager.getBindDeviceAdminTargetUsers(null)).isEmpty();
+  }
+
+  @Test
+  @Config(minSdk = O)
+  public void getBindDeviceAdminTargetUsers_returnsSetValue() {
+    List<UserHandle> targetUsers = Collections.singletonList(UserHandle.of(10));
+    shadowOf(devicePolicyManager).setBindDeviceAdminTargetUsers(targetUsers);
+
+    assertThat(devicePolicyManager.getBindDeviceAdminTargetUsers(null))
+        .containsExactlyElementsIn(targetUsers);
+  }
+
+  @Test
+  @Config(minSdk = O)
+  public void bindDeviceAdminServiceAsUser_invalidUserHandle_throwsSecurityException() {
+    UserHandle targetUser = UserHandle.of(10);
+
+    Intent serviceIntent = new Intent().setPackage("dummy.package");
+    ServiceConnection conn = buildServiceConnection();
+    int flags = 0;
+
+    try {
+      devicePolicyManager.bindDeviceAdminServiceAsUser(
+          null, serviceIntent, conn, flags, targetUser);
+      fail("Expected SecurityException");
+    } catch (SecurityException expected) {
+    }
+    assertThat(shadowOf(context).getBoundServiceConnections()).isEmpty();
+  }
+
+  @Test
+  @Config(minSdk = O)
+  public void bindDeviceAdminServiceAsUser_validUserHandle_binds() {
+    UserHandle targetUser = UserHandle.of(10);
+    shadowOf(devicePolicyManager)
+        .setBindDeviceAdminTargetUsers(Collections.singletonList(targetUser));
+
+    Intent serviceIntent = new Intent().setPackage("dummy.package");
+    ServiceConnection conn = buildServiceConnection();
+    int flags = 0;
+
+    assertThat(
+            devicePolicyManager.bindDeviceAdminServiceAsUser(
+                null, serviceIntent, conn, flags, targetUser))
+        .isTrue();
+
+    assertThat(shadowOf(context).getBoundServiceConnections()).hasSize(1);
+  }
+
+  private ServiceConnection buildServiceConnection() {
+    return new ServiceConnection() {
+      @Override
+      public void onServiceConnected(ComponentName name, IBinder service) {}
+
+      @Override
+      public void onServiceDisconnected(ComponentName name) {}
+    };
+  }
 }
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
index 32e373d..1c1f8e3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
@@ -23,7 +23,9 @@
 import android.app.admin.SystemUpdatePolicy;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -32,7 +34,9 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Process;
+import android.os.UserHandle;
 import android.text.TextUtils;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -107,6 +111,7 @@
   private Context context;
   private ApplicationPackageManager applicationPackageManager;
   private SystemUpdatePolicy policy;
+  private List<UserHandle> bindDeviceAdminTargetUsers = ImmutableList.of();
 
   private @RealObject DevicePolicyManager realObject;
 
@@ -1113,4 +1118,48 @@
   public void setSystemUpdatePolicy(SystemUpdatePolicy policy) {
     setSystemUpdatePolicy(null, policy);
   }
+
+  /**
+   * Set the list of target users that the calling device or profile owner can use when calling
+   * {@link #bindDeviceAdminServiceAsUser}.
+   *
+   * @see #getBindDeviceAdminTargetUsers(ComponentName)
+   */
+  public void setBindDeviceAdminTargetUsers(List<UserHandle> bindDeviceAdminTargetUsers) {
+    this.bindDeviceAdminTargetUsers = bindDeviceAdminTargetUsers;
+  }
+
+  /**
+   * Returns the list of target users that the calling device or profile owner can use when calling
+   * {@link #bindDeviceAdminServiceAsUser}.
+   *
+   * @see #setBindDeviceAdminTargetUsers(List)
+   */
+  @Implementation(minSdk = O)
+  protected List<UserHandle> getBindDeviceAdminTargetUsers(ComponentName admin) {
+    return bindDeviceAdminTargetUsers;
+  }
+
+  /**
+   * Bind to the same package in another user.
+   *
+   * <p>This validates that the targetUser is one from {@link
+   * #getBindDeviceAdminTargetUsers(ComponentName)} but does not actually bind to a different user,
+   * instead binding to the same user.
+   *
+   * <p>It also does not validate the service being bound to.
+   */
+  @Implementation(minSdk = O)
+  protected boolean bindDeviceAdminServiceAsUser(
+      ComponentName admin,
+      Intent serviceIntent,
+      ServiceConnection conn,
+      int flags,
+      UserHandle targetUser) {
+    if (!getBindDeviceAdminTargetUsers(admin).contains(targetUser)) {
+      throw new SecurityException("Not allowed to bind to target user id");
+    }
+
+    return context.bindServiceAsUser(serviceIntent, conn, flags, targetUser);
+  }
 }