Add security policies for checking device owner/profile owner. (#9428)

diff --git a/binder/build.gradle b/binder/build.gradle
index d18fe5e..41c4bd4 100644
--- a/binder/build.gradle
+++ b/binder/build.gradle
@@ -23,14 +23,14 @@
             }
         }
     }
-    compileSdkVersion 29
+    compileSdkVersion 30
     compileOptions {
         sourceCompatibility 1.8
         targetCompatibility 1.8
     }
     defaultConfig {
         minSdkVersion 19
-        targetSdkVersion 29
+        targetSdkVersion 30
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
index 25f215b..653ae90 100644
--- a/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
+++ b/binder/src/main/java/io/grpc/binder/SecurityPolicies.java
@@ -17,11 +17,14 @@
 package io.grpc.binder;
 
 import android.annotation.SuppressLint;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
 import android.os.Build;
+import android.os.Build.VERSION;
 import android.os.Process;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
@@ -173,6 +176,50 @@
     };
   }
 
+  /**
+   * Creates {@link SecurityPolicy} which checks if the app is a device owner app. See
+   * {@link DevicePolicyManager}.
+   */
+  public static SecurityPolicy isDeviceOwner(Context applicationContext) {
+    DevicePolicyManager devicePolicyManager =
+        (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+    return anyPackageWithUidSatisfies(
+        applicationContext,
+        pkg -> VERSION.SDK_INT >= 18 && devicePolicyManager.isDeviceOwnerApp(pkg),
+        "Rejected by device owner policy. No packages found for UID.",
+        "Rejected by device owner policy");
+  }
+
+  /**
+   * Creates {@link SecurityPolicy} which checks if the app is a profile owner app. See
+   * {@link DevicePolicyManager}.
+   */
+  public static SecurityPolicy isProfileOwner(Context applicationContext) {
+    DevicePolicyManager devicePolicyManager =
+        (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+    return anyPackageWithUidSatisfies(
+        applicationContext,
+        pkg -> VERSION.SDK_INT >= 21 && devicePolicyManager.isProfileOwnerApp(pkg),
+        "Rejected by profile owner policy. No packages found for UID.",
+        "Rejected by profile owner policy");
+  }
+
+  /**
+   * Creates {@link SecurityPolicy} which checks if the app is a profile owner app on an
+   * organization-owned device. See {@link DevicePolicyManager}.
+   */
+  public static SecurityPolicy isProfileOwnerOnOrganizationOwnedDevice(Context applicationContext) {
+    DevicePolicyManager devicePolicyManager =
+        (DevicePolicyManager) applicationContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+    return anyPackageWithUidSatisfies(
+        applicationContext,
+        pkg -> VERSION.SDK_INT >= 30
+            && devicePolicyManager.isProfileOwnerApp(pkg)
+            && devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(),
+        "Rejected by profile owner on organization-owned device policy. No packages found for UID.",
+        "Rejected by profile owner on organization-owned device policy");
+  }
+
   private static Status checkUidSignature(
       PackageManager packageManager,
       int uid,
@@ -406,6 +453,29 @@
     return Status.OK;
   }
 
+  private static SecurityPolicy anyPackageWithUidSatisfies(
+      Context applicationContext,
+      Predicate<String> condition,
+      String errorMessageForNoPackages,
+      String errorMessageForDenied) {
+    return new SecurityPolicy() {
+      @Override
+      public Status checkAuthorization(int uid) {
+        String[] packages = applicationContext.getPackageManager().getPackagesForUid(uid);
+        if (packages == null || packages.length == 0) {
+          return Status.UNAUTHENTICATED.withDescription(errorMessageForNoPackages);
+        }
+
+        for (String pkg : packages) {
+          if (condition.apply(pkg)) {
+            return Status.OK;
+          }
+        }
+        return Status.PERMISSION_DENIED.withDescription(errorMessageForDenied);
+      }
+    };
+  }
+
   /**
    * Checks if the SHA-256 hash of the {@code signature} matches one of the {@code
    * expectedSignatureSha256Hashes}.
diff --git a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
index a26d4dd..909547a 100644
--- a/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
+++ b/binder/src/test/java/io/grpc/binder/SecurityPoliciesTest.java
@@ -24,10 +24,15 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.robolectric.Shadows.shadowOf;
 
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.os.Build;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
 import android.os.Process;
 import androidx.test.core.app.ApplicationProvider;
 import com.google.common.collect.ImmutableList;
@@ -40,6 +45,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
 
 @RunWith(RobolectricTestRunner.class)
 public final class SecurityPoliciesTest {
@@ -59,6 +65,7 @@
 
   private Context appContext;
   private PackageManager packageManager;
+  private DevicePolicyManager devicePolicyManager;
 
   private SecurityPolicy policy;
 
@@ -66,6 +73,8 @@
   public void setUp() {
     appContext = ApplicationProvider.getApplicationContext();
     packageManager = appContext.getPackageManager();
+    devicePolicyManager =
+        (DevicePolicyManager) appContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
   }
 
   @SuppressWarnings("deprecation")
@@ -323,6 +332,171 @@
         .contains(OTHER_UID_PACKAGE_NAME);
   }
 
+  @Test
+  @Config(sdk = 18)
+  public void testIsDeviceOwner_succeedsForDeviceOwner() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+    shadowOf(devicePolicyManager)
+        .setDeviceOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+
+    policy = SecurityPolicies.isDeviceOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+  }
+
+  @Test
+  @Config(sdk = 18)
+  public void testIsDeviceOwner_failsForNotDeviceOwner() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+
+    policy = SecurityPolicies.isDeviceOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 18)
+  public void testIsDeviceOwner_failsWhenNoPackagesForUid() throws Exception {
+    policy = SecurityPolicies.isDeviceOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 17)
+  public void testIsDeviceOwner_failsForSdkLevelTooLow() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+
+    policy = SecurityPolicies.isDeviceOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 21)
+  public void testIsProfileOwner_succeedsForProfileOwner() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+    shadowOf(devicePolicyManager)
+        .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+
+    policy = SecurityPolicies.isProfileOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+  }
+
+  @Test
+  @Config(sdk = 21)
+  public void testIsProfileOwner_failsForNotProfileOwner() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+
+    policy = SecurityPolicies.isProfileOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 21)
+  public void testIsProfileOwner_failsWhenNoPackagesForUid() throws Exception {
+    policy = SecurityPolicies.isProfileOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 19)
+  public void testIsProfileOwner_failsForSdkLevelTooLow() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+
+    policy = SecurityPolicies.isProfileOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 30)
+  public void testIsProfileOwnerOnOrgOwned_succeedsForProfileOwnerOnOrgOwned() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+    shadowOf(devicePolicyManager)
+        .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+    shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(true);
+
+    policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+
+  }
+
+  @Test
+  @Config(sdk = 30)
+  public void testIsProfileOwnerOnOrgOwned_failsForProfileOwnerOnNonOrgOwned() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+    shadowOf(devicePolicyManager)
+        .setProfileOwner(new ComponentName(OTHER_UID_PACKAGE_NAME, "foo"));
+    shadowOf(devicePolicyManager).setOrganizationOwnedDeviceWithManagedProfile(false);
+
+    policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.OK.getCode());
+  }
+
+  @Test
+  @Config(sdk = 21)
+  public void testIsProfileOwnerOnOrgOwned_failsForNotProfileOwner() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+
+    policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 21)
+  public void testIsProfileOwnerOnOrgOwned_failsWhenNoPackagesForUid() throws Exception {
+    policy = SecurityPolicies.isProfileOwnerOnOrganizationOwnedDevice(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.UNAUTHENTICATED.getCode());
+  }
+
+  @Test
+  @Config(sdk = 29)
+  public void testIsProfileOwnerOnOrgOwned_failsForSdkLevelTooLow() throws Exception {
+    PackageInfo info =
+        newBuilder().setPackageName(OTHER_UID_PACKAGE_NAME).setSignatures(SIG2).build();
+
+    installPackages(OTHER_UID, info);
+
+    policy = SecurityPolicies.isProfileOwner(appContext);
+
+    assertThat(policy.checkAuthorization(OTHER_UID).getCode()).isEqualTo(Status.PERMISSION_DENIED.getCode());
+  }
+
   private static PackageInfoBuilder newBuilder() {
     return new PackageInfoBuilder();
   }