DevicePolicyManagerService - migrate to platform compat framework gating.

Replace targetSDK check with platform compat framework gating for admin password changes in R.
Define a new change id and use it for gating the change.

Test: com.android.cts.devicepolicy.MixedDeviceOwnerTest#testResetPasswordWithToken
Test: com.android.cts.devicepolicy.DeviceAdminHostSideTestApi23#testRunDeviceOwnerPasswordTest
Test: com.android.cts.devicepolicy.MixedDeviceOwnerTestApi25#testPasswordRequirementsApi
Bug: 123562444
Change-Id: Icf2515a0ade1fb7bf60d22b7c705346a2d75acba
diff --git a/Android.bp b/Android.bp
index b64907a..0547481 100644
--- a/Android.bp
+++ b/Android.bp
@@ -414,6 +414,7 @@
         "libcore-platform-compat-config",
         "services-platform-compat-config",
         "media-provider-platform-compat-config",
+        "services-devicepolicy-platform-compat-config",
     ],
     sdk_version: "core_platform",
 }
@@ -1595,4 +1596,4 @@
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
     ],
-}
\ No newline at end of file
+}
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 47790ce..91c05a8 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -5,4 +5,13 @@
     libs: [
         "services.core",
     ],
+
+    plugins: [
+        "compat-changeid-annotation-processor",
+    ],
 }
+
+platform_compat_config {
+    name: "services-devicepolicy-platform-compat-config",
+    src: ":services.devicepolicy",
+}
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a0b76cc..0ae205a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -137,6 +137,8 @@
 import android.app.backup.IBackupManager;
 import android.app.trust.TrustManager;
 import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -235,6 +237,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
@@ -495,6 +498,22 @@
     private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
     private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
 
+    /**
+     * For admin apps targeting R+, throw when the app sets password requirement
+     * that is not taken into account at given quality. For example when quality is set
+     * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't make sense to
+     * require certain password length. If the intent is to require a password of certain length
+     * having at least NUMERIC quality, the admin should first call
+     * {@link #setPasswordQuality(ComponentName, int, boolean)} and only then call
+     * {@link #setPasswordMinimumLength(ComponentName, int, boolean)}.
+     *
+     * <p>Conversely when an admin app targeting R+ lowers password quality, those
+     * requirements that stop making sense are reset to default values.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+    private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L;
+
     final Context mContext;
     final Injector mInjector;
     final IPackageManager mIPackageManager;
@@ -507,6 +526,7 @@
     private final LockSettingsInternal mLockSettingsInternal;
     private final DeviceAdminServiceController mDeviceAdminServiceController;
     private final OverlayPackagesProvider mOverlayPackagesProvider;
+    private final IPlatformCompat mIPlatformCompat;
 
     private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
     private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
@@ -1985,6 +2005,11 @@
             return LocalServices.getService(LockSettingsInternal.class);
         }
 
+        IPlatformCompat getIPlatformCompat() {
+            return IPlatformCompat.Stub.asInterface(
+                    ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        }
+
         boolean hasUserSetupCompleted(DevicePolicyData userData) {
             return userData.mUserSetupComplete;
         }
@@ -2223,6 +2248,7 @@
         mUsageStatsManagerInternal = Preconditions.checkNotNull(
                 injector.getUsageStatsManagerInternal());
         mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
+        mIPlatformCompat = Preconditions.checkNotNull(injector.getIPlatformCompat());
         mIPermissionManager = Preconditions.checkNotNull(injector.getIPermissionManager());
         mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager());
 
@@ -4157,12 +4183,22 @@
                 .write();
     }
 
+    private boolean passwordQualityInvocationOrderCheckEnabled(String packageName, int userId) {
+        try {
+            return mIPlatformCompat.isChangeEnabledByPackageName(ADMIN_APP_PASSWORD_COMPLEXITY,
+                    packageName);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+        }
+        return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q;
+    }
+
     /**
      * For admins targeting R+ reset various password constraints to default values when quality is
      * set to a value that makes those constraints that have no effect.
      */
     private void resetInactivePasswordRequirementsIfRPlus(int userId, ActiveAdmin admin) {
-        if (getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
+        if (passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(), userId)) {
             final PasswordMetrics metrics = admin.minimumPasswordMetrics;
             if (metrics.quality < PASSWORD_QUALITY_NUMERIC) {
                 metrics.length = ActiveAdmin.DEF_MINIMUM_PASSWORD_LENGTH;
@@ -4327,7 +4363,8 @@
     private void ensureMinimumQuality(
             int userId, ActiveAdmin admin, int minimumQuality, String operation) {
         if (admin.minimumPasswordMetrics.quality < minimumQuality
-                && getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
+                && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
+                userId)) {
             throw new IllegalStateException(String.format(
                     "password quality should be at least %d for %s", minimumQuality, operation));
         }