[automerger skipped] Merge "Import translations. DO NOT MERGE ANYWHERE" into main am: 3181c30c40 -s ours am: 445ea15f21 -s ours

am skip reason: contains skip directive

Original change: https://android-review.googlesource.com/c/platform/packages/modules/DeviceLock/+/3263816

Change-Id: Ib26a3c1a39ac0853f34e7eedb8023d15532f1fad
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/DeviceLockControllerService.java b/DeviceLockController/src/com/android/devicelockcontroller/DeviceLockControllerService.java
index 8212512..7b99931 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/DeviceLockControllerService.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/DeviceLockControllerService.java
@@ -17,7 +17,6 @@
 package com.android.devicelockcontroller;
 
 import android.app.Service;
-import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.devicelock.ParcelableException;
@@ -43,9 +42,6 @@
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 
-import java.util.List;
-import java.util.Objects;
-
 /**
  * Device Lock Controller Service. This is hosted in an APK and is bound
  * by the Device Lock System Service.
@@ -127,10 +123,6 @@
 
                 @Override
                 public void onUserUnlocked(RemoteCallback remoteCallback) {
-                    DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class);
-                    Objects.requireNonNull(dpm).setUserControlDisabledPackages(
-                            /* admin= */ null,
-                            List.of(getPackageName()));
                     Futures.addCallback(mPolicyController.onUserUnlocked(),
                             remoteCallbackWrapper(remoteCallback),
                             MoreExecutors.directExecutor());
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImpl.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImpl.java
index fa91477..de3c631 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImpl.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImpl.java
@@ -134,14 +134,14 @@
     DevicePolicyControllerImpl(Context context,
             DevicePolicyManager devicePolicyManager,
             UserManager userManager,
-            UserRestrictionsPolicyHandler userRestrictionsPolicyHandler,
-            AppOpsPolicyHandler appOpsPolicyHandler,
-            LockTaskModePolicyHandler lockTaskModePolicyHandler,
-            PackagePolicyHandler packagePolicyHandler,
-            RolePolicyHandler rolePolicyHandler,
-            KioskKeepAlivePolicyHandler kioskKeepAlivePolicyHandler,
-            ControllerKeepAlivePolicyHandler controllerKeepAlivePolicyHandler,
-            NotificationsPolicyHandler notificationsPolicyHandler,
+            PolicyHandler userRestrictionsPolicyHandler,
+            PolicyHandler appOpsPolicyHandler,
+            PolicyHandler lockTaskModePolicyHandler,
+            PolicyHandler packagePolicyHandler,
+            PolicyHandler rolePolicyHandler,
+            PolicyHandler kioskKeepAlivePolicyHandler,
+            PolicyHandler controllerKeepAlivePolicyHandler,
+            PolicyHandler notificationsPolicyHandler,
             ProvisionStateController provisionStateController,
             Executor bgExecutor) {
         mContext = context;
@@ -258,9 +258,6 @@
             @ProvisionState int provisionState, @DeviceState int deviceState) {
         LogUtil.i(TAG, "Enforcing policies for provision state " + provisionState
                 + " and device state " + deviceState);
-        if (provisionState == UNPROVISIONED) {
-            return Futures.immediateFuture(resolveLockTaskType(provisionState, deviceState));
-        }
         List<ListenableFuture<Boolean>> futures = new ArrayList<>();
         if (deviceState == CLEARED) {
             // If device is cleared, then ignore provision state and add cleared policies
@@ -292,6 +289,9 @@
             for (int i = 0, policyLen = mPolicyList.size(); i < policyLen; i++) {
                 PolicyHandler policy = mPolicyList.get(i);
                 switch (provisionState) {
+                    case UNPROVISIONED:
+                        futures.add(policy.onUnprovisioned());
+                        break;
                     case PROVISION_IN_PROGRESS:
                         futures.add(policy.onProvisionInProgress());
                         break;
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/DeviceStateControllerImpl.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/DeviceStateControllerImpl.java
index 6c623a2..82b9f8f 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/DeviceStateControllerImpl.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/DeviceStateControllerImpl.java
@@ -25,6 +25,9 @@
 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED;
 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED;
 
+import androidx.annotation.VisibleForTesting;
+
+import com.android.devicelock.flags.Flags;
 import com.android.devicelockcontroller.storage.GlobalParametersClient;
 
 import com.google.common.util.concurrent.Futures;
@@ -42,7 +45,8 @@
     // Used to exercising APIs under CTS without actually applying any policies.
     // This is not persistent across controller restarts, but should be good enough for the
     // intended purpose.
-    private volatile @DeviceState int mPseudoDeviceState;
+    @VisibleForTesting
+    volatile @DeviceState int mPseudoDeviceState;
 
     public DeviceStateControllerImpl(DevicePolicyController policyController,
             ProvisionStateController provisionStateController, Executor executor) {
@@ -92,6 +96,13 @@
                         mPseudoDeviceState = deviceState;
                         // Do not apply any policies
                         return Futures.immediateVoidFuture();
+                    } else if (Flags.clearDeviceRestrictions()
+                            && (provisionState == UNPROVISIONED && deviceState == CLEARED)) {
+                        // During normal operation, we should not get clear requests in
+                        // the UNPROVISIONED state. Used for CTS compliance.
+                        mPseudoDeviceState = deviceState;
+                        // Do not apply any policies
+                        return Futures.immediateVoidFuture();
                     } else {
                         throw new RuntimeException(
                                 "User has not been provisioned! Current state " + provisionState);
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/FinalizationControllerImpl.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/FinalizationControllerImpl.java
index af36d97..935f686 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/FinalizationControllerImpl.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/FinalizationControllerImpl.java
@@ -16,11 +16,6 @@
 
 package com.android.devicelockcontroller.policy;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-
 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.FINALIZED;
 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.FINALIZED_UNREPORTED;
 import static com.android.devicelockcontroller.policy.FinalizationControllerImpl.FinalizationState.UNFINALIZED;
@@ -33,7 +28,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.NetworkRequest;
 import android.os.OutcomeReceiver;
 
 import androidx.annotation.NonNull;
@@ -244,14 +238,8 @@
     private void requestWorkToReportFinalized() {
         WorkManager workManager =
                 WorkManager.getInstance(mContext);
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NET_CAPABILITY_TRUSTED)
-                .addCapability(NET_CAPABILITY_INTERNET)
-                .addCapability(NET_CAPABILITY_NOT_VPN)
-                .build();
         Constraints constraints = new Constraints.Builder()
-                .setRequiredNetworkRequest(request, NetworkType.CONNECTED)
+                .setRequiredNetworkType(NetworkType.CONNECTED)
                 .build();
         OneTimeWorkRequest work =
                 new OneTimeWorkRequest.Builder(mReportDeviceFinalizedWorkerClass)
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandler.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandler.java
index 71553e5..19bc77d 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandler.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandler.java
@@ -100,6 +100,16 @@
     }
 
     @Override
+    public ListenableFuture<Boolean> onLocked() {
+        return getEnableKioskKeepAliveFuture();
+    }
+
+    @Override
+    public ListenableFuture<Boolean> onUnlocked() {
+        return getDisableKioskKeepAliveFuture();
+    }
+
+    @Override
     public ListenableFuture<Boolean> onProvisioned() {
         return getEnableKioskKeepAliveFuture();
     }
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/LockTaskModePolicyHandler.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/LockTaskModePolicyHandler.java
index c0c4a10..1fc3cd4 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/LockTaskModePolicyHandler.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/LockTaskModePolicyHandler.java
@@ -178,17 +178,19 @@
             if (mUserManager.isUserUnlocked()) {
                 WorkManager.getInstance(mContext).cancelUniqueWork(START_LOCK_TASK_MODE_WORK_NAME);
             }
-
             // Device Policy Engine treats lock task features and packages as one policy and
             // therefore we need to set both lock task features (to LOCK_TASK_FEATURE_NONE) and
             // lock task packages (to an empty string array).
-            mDpm.setLockTaskFeatures(null /* admin */, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
+
             // This is a hacky workaround to stop the lock task mode by enforcing that no apps
             // can be in lock task mode
             // TODO(b/288886570): Fix this in the framework so we don't have to do this workaround
             mDpm.setLockTaskPackages(null /* admin */, new String[]{""});
             // This will remove the DLC policy and allow other admins to enforce their policy
             mDpm.setLockTaskPackages(null /* admin */, new String[0]);
+            // Set lock task features (to LOCK_TASK_FEATURE_NONE) after removing the DLC policy
+            // in order to prevent keyguard from being disabled while lock task is still active.
+            mDpm.setLockTaskFeatures(null /* admin */, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
             mDpm.clearPackagePersistentPreferredActivities(null /* admin */,
                     mContext.getPackageName());
             ComponentName lockedHomeActivity =
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/PackagePolicyHandler.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/PackagePolicyHandler.java
index 97db153..6ffd600 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/PackagePolicyHandler.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/PackagePolicyHandler.java
@@ -44,6 +44,11 @@
     }
 
     @Override
+    public ListenableFuture<Boolean> onUnprovisioned() {
+        return enablePackageProtection(/* enableForKiosk= */ false);
+    }
+
+    @Override
     public ListenableFuture<Boolean> onProvisioned() {
         return enablePackageProtection(/* enableForKiosk= */ true);
     }
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/PolicyHandler.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/PolicyHandler.java
index 15ce64b..b2e6f8e 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/PolicyHandler.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/PolicyHandler.java
@@ -24,6 +24,10 @@
  */
 interface PolicyHandler {
 
+    default ListenableFuture<Boolean> onUnprovisioned() {
+        return Futures.immediateFuture(true);
+    }
+
     default ListenableFuture<Boolean> onProvisioned() {
         return Futures.immediateFuture(true);
     }
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/policy/ProvisionHelperImpl.java b/DeviceLockController/src/com/android/devicelockcontroller/policy/ProvisionHelperImpl.java
index 5248721..d30dd78 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/policy/ProvisionHelperImpl.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/policy/ProvisionHelperImpl.java
@@ -16,11 +16,6 @@
 
 package com.android.devicelockcontroller.policy;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-
 import static androidx.work.WorkInfo.State.CANCELLED;
 import static androidx.work.WorkInfo.State.FAILED;
 import static androidx.work.WorkInfo.State.SUCCEEDED;
@@ -37,7 +32,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.sqlite.SQLiteException;
-import android.net.NetworkRequest;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -367,15 +361,9 @@
 
     @NonNull
     private static OneTimeWorkRequest getIsDeviceInApprovedCountryWork() {
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NET_CAPABILITY_TRUSTED)
-                .addCapability(NET_CAPABILITY_INTERNET)
-                .addCapability(NET_CAPABILITY_NOT_VPN)
-                .build();
         return new OneTimeWorkRequest.Builder(IsDeviceInApprovedCountryWorker.class)
-                .setConstraints(new Constraints.Builder().setRequiredNetworkRequest(
-                        request, NetworkType.CONNECTED).build())
+                .setConstraints(new Constraints.Builder().setRequiredNetworkType(
+                        NetworkType.CONNECTED).build())
                 // Set the request as expedited and use a short retry backoff time since the
                 // user is in the setup flow while we check if the device is in an approved country
                 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/PauseProvisioningWorker.java b/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/PauseProvisioningWorker.java
index dc38af0..7303450 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/PauseProvisioningWorker.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/PauseProvisioningWorker.java
@@ -16,16 +16,10 @@
 
 package com.android.devicelockcontroller.provision.worker;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-
 import static com.android.devicelockcontroller.common.DeviceLockConstants.REASON_UNSPECIFIED;
 import static com.android.devicelockcontroller.common.DeviceLockConstants.USER_DEFERRED_DEVICE_PROVISIONING;
 
 import android.content.Context;
-import android.net.NetworkRequest;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -70,14 +64,8 @@
         Data inputData = new Data.Builder()
                 .putInt(KEY_PAUSE_DEVICE_PROVISIONING_REASON, USER_DEFERRED_DEVICE_PROVISIONING)
                 .build();
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NET_CAPABILITY_TRUSTED)
-                .addCapability(NET_CAPABILITY_INTERNET)
-                .addCapability(NET_CAPABILITY_NOT_VPN)
-                .build();
         Constraints constraints = new Constraints.Builder()
-                .setRequiredNetworkRequest(request, NetworkType.CONNECTED)
+                .setRequiredNetworkType(NetworkType.CONNECTED)
                 .build();
         OneTimeWorkRequest work =
                 new OneTimeWorkRequest.Builder(PauseProvisioningWorker.class)
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/ReportDeviceProvisionStateWorker.java b/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/ReportDeviceProvisionStateWorker.java
index 886d4b2..8d48e62 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/ReportDeviceProvisionStateWorker.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/provision/worker/ReportDeviceProvisionStateWorker.java
@@ -16,11 +16,6 @@
 
 package com.android.devicelockcontroller.provision.worker;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-
 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_DISMISSIBLE_UI;
 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_FACTORY_RESET;
 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_PERSISTENT_UI;
@@ -30,7 +25,6 @@
 import static com.android.devicelockcontroller.common.DeviceLockConstants.ProvisionFailureReason.DEADLINE_PASSED;
 
 import android.content.Context;
-import android.net.NetworkRequest;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -110,14 +104,8 @@
     }
 
     private static void enqueueReportWork(Data inputData, WorkManager workManager) {
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NET_CAPABILITY_TRUSTED)
-                .addCapability(NET_CAPABILITY_INTERNET)
-                .addCapability(NET_CAPABILITY_NOT_VPN)
-                .build();
         Constraints constraints = new Constraints.Builder()
-                .setRequiredNetworkRequest(request, NetworkType.CONNECTED)
+                .setRequiredNetworkType(NetworkType.CONNECTED)
                 .build();
         OneTimeWorkRequest work =
                 new OneTimeWorkRequest.Builder(ReportDeviceProvisionStateWorker.class)
diff --git a/DeviceLockController/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImpl.java b/DeviceLockController/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImpl.java
index 878a3bf..adfd5cd 100644
--- a/DeviceLockController/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImpl.java
+++ b/DeviceLockController/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImpl.java
@@ -16,11 +16,6 @@
 
 package com.android.devicelockcontroller.schedule;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-
 import static com.android.devicelockcontroller.WorkManagerExceptionHandler.AlarmReason;
 import static com.android.devicelockcontroller.common.DeviceLockConstants.MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE;
 import static com.android.devicelockcontroller.common.DeviceLockConstants.NON_MANDATORY_PROVISION_DEVICE_RESET_COUNTDOWN_MINUTE;
@@ -35,7 +30,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.net.NetworkRequest;
 import android.os.Build;
 import android.os.SystemClock;
 
@@ -487,16 +481,10 @@
     }
 
     private Operation enqueueCheckInWorkRequest(boolean isExpedited, Duration delay) {
-        NetworkRequest request = new NetworkRequest.Builder()
-                .addCapability(NET_CAPABILITY_NOT_RESTRICTED)
-                .addCapability(NET_CAPABILITY_TRUSTED)
-                .addCapability(NET_CAPABILITY_INTERNET)
-                .addCapability(NET_CAPABILITY_NOT_VPN)
-                .build();
         OneTimeWorkRequest.Builder builder =
                 new OneTimeWorkRequest.Builder(DeviceCheckInWorker.class)
                         .setConstraints(
-                                new Constraints.Builder().setRequiredNetworkRequest(request,
+                                new Constraints.Builder().setRequiredNetworkType(
                                         NetworkType.CONNECTED).build())
                         .setInitialDelay(delay)
                         .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, BACKOFF_DELAY);
diff --git a/DeviceLockController/tests/robolectric/Android.bp b/DeviceLockController/tests/robolectric/Android.bp
index 70ecd33..161fd64 100644
--- a/DeviceLockController/tests/robolectric/Android.bp
+++ b/DeviceLockController/tests/robolectric/Android.bp
@@ -18,6 +18,7 @@
 
 android_robolectric_test {
     name: "DeviceLockControllerRoboTests",
+    team: "trendy_team_android_go",
     instrumentation_for: "DeviceLockController",
     upstream: true,
     java_resource_dirs: [
@@ -32,9 +33,11 @@
         "guava-android-testlib",
         "grpc-java-lite",
         "grpc-java-testing",
+        "flag-junit",
     ],
     libs: [
         "androidx.work_work-testing",
+        "devicelock-aconfig-flags-lib",
     ],
     test_suites: ["general-tests"],
 
diff --git a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImplEnforcementTest.java b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImplEnforcementTest.java
new file mode 100644
index 0000000..1ca8ce4
--- /dev/null
+++ b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImplEnforcementTest.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2024 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.devicelockcontroller.policy;
+
+import static com.android.devicelockcontroller.policy.DeviceStateController.DeviceState.UNDEFINED;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.UserManager;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.devicelockcontroller.TestDeviceLockControllerApplication;
+import com.android.devicelockcontroller.storage.GlobalParametersClient;
+
+import com.google.common.util.concurrent.Futures;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DevicePolicyControllerImplEnforcementTest {
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Mock
+    private ProvisionStateController mMockProvisionStateController;
+    @Mock
+    private DevicePolicyManager mMockDpm;
+    @Mock
+    private UserManager mMockUserManager;
+    @Mock
+    private PolicyHandler mMockUserRestrictionsPolicyHandler;
+    @Mock
+    private PolicyHandler mMockAppOpsPolicyHandler;
+    @Mock
+    private PolicyHandler mMockLockTaskModePolicyHandler;
+    @Mock
+    private PolicyHandler mMockPackagePolicyHandler;
+    @Mock
+    private PolicyHandler mMockRolePolicyHandler;
+    @Mock
+    private PolicyHandler mMockKioskKeepAlivePolicyHandler;
+    @Mock
+    private PolicyHandler mMockControllerKeepAlivePolicyHandler;
+    @Mock
+    private PolicyHandler mMockNotificationsPolicyHandler;
+
+    private DevicePolicyController mDevicePolicyController;
+    private TestDeviceLockControllerApplication mTestApp;
+
+    private void setupPolicyHandler(PolicyHandler policyHandler) {
+        when(policyHandler.onUnprovisioned()).thenReturn(Futures.immediateFuture(true));
+        when(policyHandler.onProvisioned()).thenReturn(Futures.immediateFuture(true));
+        when(policyHandler.onProvisionInProgress()).thenReturn(Futures.immediateFuture(true));
+        when(policyHandler.onProvisionPaused()).thenReturn(Futures.immediateFuture(true));
+        when(policyHandler.onProvisionFailed()).thenReturn(Futures.immediateFuture(true));
+        when(policyHandler.onLocked()).thenReturn(Futures.immediateFuture(true));
+        when(policyHandler.onUnlocked()).thenReturn(Futures.immediateFuture(true));
+        when(policyHandler.onCleared()).thenReturn(Futures.immediateFuture(true));
+    }
+
+    @Before
+    public void setUp() {
+        mTestApp = ApplicationProvider.getApplicationContext();
+        ExecutorService bgExecutor = Executors.newSingleThreadExecutor();
+
+        setupPolicyHandler(mMockUserRestrictionsPolicyHandler);
+        setupPolicyHandler(mMockAppOpsPolicyHandler);
+        setupPolicyHandler(mMockLockTaskModePolicyHandler);
+        setupPolicyHandler(mMockPackagePolicyHandler);
+        setupPolicyHandler(mMockRolePolicyHandler);
+        setupPolicyHandler(mMockKioskKeepAlivePolicyHandler);
+        setupPolicyHandler(mMockControllerKeepAlivePolicyHandler);
+        setupPolicyHandler(mMockNotificationsPolicyHandler);
+
+        mDevicePolicyController =
+                new DevicePolicyControllerImpl(mTestApp,
+                        mMockDpm,
+                        mMockUserManager,
+                        mMockUserRestrictionsPolicyHandler,
+                        mMockAppOpsPolicyHandler,
+                        mMockLockTaskModePolicyHandler,
+                        mMockPackagePolicyHandler,
+                        mMockRolePolicyHandler,
+                        mMockKioskKeepAlivePolicyHandler,
+                        mMockControllerKeepAlivePolicyHandler,
+                        mMockNotificationsPolicyHandler,
+                        mMockProvisionStateController,
+                        bgExecutor);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_withProvisionStateUnprovisioned_shouldCallOnUnprovisioned()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.UNPROVISIONED));
+        GlobalParametersClient.getInstance().setDeviceState(UNDEFINED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onUnprovisioned();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_withProvisionStateProgress_shouldCallOnProvisionInProgress()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.PROVISION_IN_PROGRESS));
+        GlobalParametersClient.getInstance().setDeviceState(UNDEFINED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onProvisionInProgress();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_withProvisionStateKioskProvisioned_shouldCallOnProvisioned()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.KIOSK_PROVISIONED));
+        GlobalParametersClient.getInstance().setDeviceState(UNDEFINED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onProvisioned();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_withProvisionStatePaused_shouldCallOnProvisionPaused()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.PROVISION_PAUSED));
+        GlobalParametersClient.getInstance().setDeviceState(UNDEFINED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onProvisionPaused();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_withProvisionStateFailed_shouldCallOnProvisionFailed()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.PROVISION_FAILED));
+        GlobalParametersClient.getInstance().setDeviceState(UNDEFINED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onProvisionFailed();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_provisionSucceeded_deviceUnlocked_shouldCallOnUnlocked()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED));
+        GlobalParametersClient.getInstance().setDeviceState(
+                DeviceStateController.DeviceState.UNLOCKED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onUnlocked();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_provisionSucceeded_deviceLocked_shouldCallOnLocked()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED));
+        GlobalParametersClient.getInstance().setDeviceState(
+                DeviceStateController.DeviceState.LOCKED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onLocked();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_provisionSucceeded_deviceStateUndefined_shouldDoNothing()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.PROVISION_SUCCEEDED));
+        GlobalParametersClient.getInstance().setDeviceState(UNDEFINED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verifyNoInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verifyNoInteractions(mMockAppOpsPolicyHandler);
+
+        verifyNoInteractions(mMockLockTaskModePolicyHandler);
+
+        verifyNoInteractions(mMockPackagePolicyHandler);
+
+        verifyNoInteractions(mMockRolePolicyHandler);
+
+        verifyNoInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verifyNoInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verifyNoInteractions(mMockNotificationsPolicyHandler);
+    }
+
+    @Test
+    public void enforceCurrentPolicies_withDeviceStateCleared_shouldCallOnCleared()
+            throws ExecutionException, InterruptedException {
+        // Provision state can be anything.
+        when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
+                ProvisionStateController.ProvisionState.UNPROVISIONED));
+        GlobalParametersClient.getInstance().setDeviceState(
+                DeviceStateController.DeviceState.CLEARED).get();
+
+        mDevicePolicyController.enforceCurrentPolicies().get();
+
+        verify(mMockUserRestrictionsPolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockUserRestrictionsPolicyHandler);
+
+        verify(mMockAppOpsPolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockAppOpsPolicyHandler);
+
+        verify(mMockLockTaskModePolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockLockTaskModePolicyHandler);
+
+        verify(mMockPackagePolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockPackagePolicyHandler);
+
+        verify(mMockRolePolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockRolePolicyHandler);
+
+        verify(mMockKioskKeepAlivePolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockKioskKeepAlivePolicyHandler);
+
+        verify(mMockControllerKeepAlivePolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockControllerKeepAlivePolicyHandler);
+
+        verify(mMockNotificationsPolicyHandler).onCleared();
+        verifyNoMoreInteractions(mMockNotificationsPolicyHandler);
+    }
+}
diff --git a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImplTest.java b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImplTest.java
index 938a4b4..ad2b2ea 100644
--- a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImplTest.java
+++ b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DevicePolicyControllerImplTest.java
@@ -287,6 +287,7 @@
             throws Exception {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnEnableKioskKeepAlive();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
                 ProvisionState.PROVISION_SUCCEEDED));
         when(mMockUserManager.isUserUnlocked()).thenReturn(true);
@@ -303,6 +304,7 @@
             throws Exception {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnDisableKioskKeepAlive();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
                 ProvisionState.PROVISION_SUCCEEDED));
         when(mMockUserManager.isUserUnlocked()).thenReturn(true);
@@ -361,6 +363,7 @@
             throws Exception {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnEnableKioskKeepAlive();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
                 ProvisionState.PROVISION_SUCCEEDED));
         when(mMockUserManager.isUserUnlocked()).thenReturn(true);
@@ -546,6 +549,7 @@
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
         setExpectationsOnDisableControllerKeepAlive();
+        setExpectationsOnEnableKioskKeepAlive();
         GlobalParametersClient.getInstance().setDeviceState(LOCKED).get();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
                 ProvisionState.PROVISION_SUCCEEDED));
@@ -562,6 +566,7 @@
             throws ExecutionException, InterruptedException {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnEnableKioskKeepAlive();
         GlobalParametersClient.getInstance().setDeviceState(LOCKED).get();
         installKioskAppWithLockScreenIntentFilter();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
@@ -582,6 +587,7 @@
             throws ExecutionException, InterruptedException {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnEnableKioskKeepAlive();
         GlobalParametersClient.getInstance().setDeviceState(LOCKED).get();
         installKioskAppWithoutCategoryHomeIntentFilter();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
@@ -601,6 +607,7 @@
             throws ExecutionException, InterruptedException {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnDisableKioskKeepAlive();
         GlobalParametersClient.getInstance().setDeviceState(UNLOCKED).get();
         installKioskAppWithoutCategoryHomeIntentFilter();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
@@ -917,6 +924,7 @@
     public void onUserUnlocked_withLockedDeviceState_shouldMakeExpectedCalls() throws Exception {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnEnableKioskKeepAlive();
         when(mMockProvisionStateController.onUserUnlocked()).thenReturn(
                 Futures.immediateVoidFuture());
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
@@ -934,6 +942,7 @@
     public void onUserUnlocked_withUnlockedDeviceState_shouldMakeExpectedCalls() throws Exception {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnDisableKioskKeepAlive();
         when(mMockProvisionStateController.onUserUnlocked()).thenReturn(
                 Futures.immediateVoidFuture());
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
@@ -1065,6 +1074,7 @@
     public void onAppCrashed_withLockedDeviceState_shouldMakeExpectedCalls() throws Exception {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnEnableKioskKeepAlive();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
                 ProvisionState.PROVISION_SUCCEEDED));
         when(mMockUserManager.isUserUnlocked()).thenReturn(true);
@@ -1081,6 +1091,7 @@
             throws Exception {
         setupSetupParameters();
         setupAppOpsPolicyHandlerExpectations();
+        setExpectationsOnDisableKioskKeepAlive();
         when(mMockProvisionStateController.getState()).thenReturn(Futures.immediateFuture(
                 ProvisionState.PROVISION_SUCCEEDED));
         when(mMockUserManager.isUserUnlocked()).thenReturn(true);
diff --git a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DeviceStateControllerImplTest.java b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DeviceStateControllerImplTest.java
index 458b843..4983640 100644
--- a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DeviceStateControllerImplTest.java
+++ b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/DeviceStateControllerImplTest.java
@@ -22,6 +22,10 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.devicelock.flags.Flags;
 import com.android.devicelockcontroller.policy.DeviceStateController.DeviceState;
 import com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionEvent;
 import com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState;
@@ -48,6 +52,9 @@
     @Rule
     public MockitoRule mMockitoRule = MockitoJUnit.rule();
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Mock
     private DevicePolicyController mMockDevicePolicyController;
 
@@ -74,6 +81,9 @@
         // Should not have changed the real device state
         assertThat(GlobalParametersClient.getInstance().getDeviceState().get()).isEqualTo(
                 DeviceState.UNDEFINED);
+
+        assertThat(((DeviceStateControllerImpl) mDeviceStateController).mPseudoDeviceState)
+                .isEqualTo(DeviceState.LOCKED);
     }
 
     @Test
@@ -193,6 +203,9 @@
         // Should not have changed the real device state
         assertThat(GlobalParametersClient.getInstance().getDeviceState().get()).isEqualTo(
                 DeviceState.UNDEFINED);
+
+        assertThat(((DeviceStateControllerImpl) mDeviceStateController).mPseudoDeviceState)
+                .isEqualTo(DeviceState.UNLOCKED);
     }
 
     @Test
@@ -370,6 +383,24 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS)
+    public void clearDevice_withUnprovisionedState_shouldNotThrowException()
+            throws ExecutionException, InterruptedException {
+        when(mMockProvisionStateController.getState()).thenReturn(
+                Futures.immediateFuture(ProvisionState.UNPROVISIONED));
+
+        // Clearing the device state should not throw an exception.
+        mDeviceStateController.clearDevice().get();
+
+        // Should not have changed the real device state
+        assertThat(GlobalParametersClient.getInstance().getDeviceState().get()).isEqualTo(
+                DeviceState.UNDEFINED);
+
+        assertThat(((DeviceStateControllerImpl) mDeviceStateController).mPseudoDeviceState)
+                .isEqualTo(DeviceState.CLEARED);
+    }
+
+    @Test
     public void getDeviceState_shouldReturnResultFromGlobalParametersClient()
             throws ExecutionException, InterruptedException {
         GlobalParametersClient globalParametersClient = GlobalParametersClient.getInstance();
diff --git a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandlerTest.java b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandlerTest.java
index 65858c4..2e057c7 100644
--- a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandlerTest.java
+++ b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/KioskKeepAlivePolicyHandlerTest.java
@@ -127,15 +127,43 @@
     }
 
     @Test
-    public void onLocked_shouldDoNothing() throws ExecutionException, InterruptedException {
+    public void onLocked_enablesKioskKeepalive()
+            throws ExecutionException, InterruptedException {
+        setExpectationsOnEnableKioskKeepalive(/* isSuccess =*/ true);
         assertThat(mHandler.onLocked().get()).isTrue();
-        verifyNoInteractions(mSystemDeviceLockManager);
+        verify(mSystemDeviceLockManager).enableKioskKeepalive(eq(TEST_KIOSK_PACKAGE),
+                any(Executor.class), any());
+        verify(mSystemDeviceLockManager, never()).disableKioskKeepalive(any(Executor.class), any());
     }
 
     @Test
-    public void onUnlocked_shouldDoNothing() throws ExecutionException, InterruptedException {
+    public void onLocked_onFailure_stillReturnsTrue()
+            throws ExecutionException, InterruptedException {
+        setExpectationsOnEnableKioskKeepalive(/* isSuccess =*/ false);
+        assertThat(mHandler.onLocked().get()).isTrue();
+        verify(mSystemDeviceLockManager).enableKioskKeepalive(eq(TEST_KIOSK_PACKAGE),
+                any(Executor.class), any());
+        verify(mSystemDeviceLockManager, never()).disableKioskKeepalive(any(Executor.class), any());
+    }
+
+    @Test
+    public void onUnlocked_disablesKioskKeepalive()
+            throws ExecutionException, InterruptedException {
+        setExpectationsOnDisableKioskKeepalive(/* isSuccess =*/ true);
         assertThat(mHandler.onUnlocked().get()).isTrue();
-        verifyNoInteractions(mSystemDeviceLockManager);
+        verify(mSystemDeviceLockManager).disableKioskKeepalive(any(Executor.class), any());
+        verify(mSystemDeviceLockManager, never()).enableKioskKeepalive(eq(TEST_KIOSK_PACKAGE),
+                any(Executor.class), any());
+    }
+
+    @Test
+    public void onUnlocked_onFailure_stillReturnsTrue()
+            throws ExecutionException, InterruptedException {
+        setExpectationsOnDisableKioskKeepalive(/* isSuccess =*/ false);
+        assertThat(mHandler.onUnlocked().get()).isTrue();
+        verify(mSystemDeviceLockManager).disableKioskKeepalive(any(Executor.class), any());
+        verify(mSystemDeviceLockManager, never()).enableKioskKeepalive(eq(TEST_KIOSK_PACKAGE),
+                any(Executor.class), any());
     }
 
     private void setExpectationsOnEnableKioskKeepalive(boolean isSuccess) {
diff --git a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/PackagePolicyHandlerTest.java b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/PackagePolicyHandlerTest.java
index 71faa7b..a9dcd09 100644
--- a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/PackagePolicyHandlerTest.java
+++ b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/policy/PackagePolicyHandlerTest.java
@@ -77,6 +77,20 @@
     }
 
     @Test
+    public void onUnprovisioned_shouldSetUserControlDisabledPackagesForController()
+            throws ExecutionException, InterruptedException {
+
+        assertThat(mHandler.onUnprovisioned().get()).isTrue();
+
+        verify(mMockDpm).setUserControlDisabledPackages(eq(null),
+                mUserControlDisabledPackages.capture());
+        List<String> userControlDisabledPackages = mUserControlDisabledPackages.getValue();
+
+        assertThat(userControlDisabledPackages).hasSize(1);
+        assertThat(userControlDisabledPackages).contains(mContext.getPackageName());
+    }
+
+    @Test
     public void onProvisioned_withKioskPackageSet_shouldHaveExpectedMethodCalls()
             throws ExecutionException, InterruptedException {
         setupSetupParameters();
diff --git a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/receivers/NextProvisionFailedStepReceiverTest.java b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/receivers/NextProvisionFailedStepReceiverTest.java
index d6ea977..14c918a 100644
--- a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/receivers/NextProvisionFailedStepReceiverTest.java
+++ b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/receivers/NextProvisionFailedStepReceiverTest.java
@@ -16,11 +16,6 @@
 
 package com.android.devicelockcontroller.receivers;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-
 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_DISMISSIBLE_UI;
 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_PERSISTENT_UI;
 import static com.android.devicelockcontroller.common.DeviceLockConstants.DeviceProvisionState.PROVISION_STATE_RETRY;
@@ -33,12 +28,12 @@
 import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
 
 import android.content.Intent;
-import android.net.NetworkRequest;
 import android.os.Handler;
 import android.os.HandlerThread;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.work.Configuration;
+import androidx.work.NetworkType;
 import androidx.work.WorkInfo;
 import androidx.work.WorkManager;
 import androidx.work.testing.SynchronousExecutor;
@@ -157,11 +152,8 @@
                 REPORT_PROVISION_STATE_WORK_NAME).get();
         assertThat(actualWorks.size()).isEqualTo(1);
         WorkInfo actualWorkInfo = actualWorks.get(0);
-        NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+        assertThat(actualWorkInfo.getConstraints().getRequiredNetworkType()).isEqualTo(
+                NetworkType.CONNECTED);
     }
 
     private static void verifyReportProvisionStateWorkNotScheduled(WorkManager workManager)
diff --git a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImplTest.java b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImplTest.java
index 5ac605a..849d016 100644
--- a/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImplTest.java
+++ b/DeviceLockController/tests/robolectric/src/com/android/devicelockcontroller/schedule/DeviceLockControllerSchedulerImplTest.java
@@ -16,11 +16,6 @@
 
 package com.android.devicelockcontroller.schedule;
 
-import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
-
 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.PROVISION_PAUSED;
 import static com.android.devicelockcontroller.policy.ProvisionStateController.ProvisionState.UNPROVISIONED;
 import static com.android.devicelockcontroller.schedule.DeviceLockControllerSchedulerImpl.DEVICE_CHECK_IN_WORK_NAME;
@@ -29,12 +24,12 @@
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
-import android.net.NetworkRequest;
 import android.os.SystemClock;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.work.Configuration;
 import androidx.work.ExistingWorkPolicy;
+import androidx.work.NetworkType;
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.WorkInfo;
 import androidx.work.WorkManager;
@@ -257,11 +252,8 @@
                 DEVICE_CHECK_IN_WORK_NAME));
         assertThat(actualWorks.size()).isEqualTo(1);
         WorkInfo actualWorkInfo = actualWorks.get(0);
-        NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+        assertThat(actualWorkInfo.getConstraints().getRequiredNetworkType()).isEqualTo(
+                NetworkType.CONNECTED);
         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(0);
     }
 
@@ -280,11 +272,8 @@
                 DEVICE_CHECK_IN_WORK_NAME));
         assertThat(actualWorks.size()).isEqualTo(1);
         WorkInfo actualWorkInfo = actualWorks.get(0);
-        NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+        assertThat(actualWorkInfo.getConstraints().getRequiredNetworkType()).isEqualTo(
+                NetworkType.CONNECTED);
         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(
                 TEST_RETRY_CHECK_IN_DELAY.toMillis());
 
@@ -318,11 +307,8 @@
                 DEVICE_CHECK_IN_WORK_NAME));
         assertThat(actualWorks.size()).isEqualTo(1);
         WorkInfo actualWorkInfo = actualWorks.get(0);
-        NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+        assertThat(actualWorkInfo.getConstraints().getRequiredNetworkType()).isEqualTo(
+                NetworkType.CONNECTED);
 
         long expectedDelay = TEST_NEXT_CHECK_IN_TIME_MILLIS - TEST_CURRENT_TIME_MILLIS;
         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(expectedDelay);
@@ -390,11 +376,8 @@
                 DEVICE_CHECK_IN_WORK_NAME));
         assertThat(actualWorks.size()).isEqualTo(1);
         WorkInfo actualWorkInfo = actualWorks.get(0);
-        NetworkRequest networkRequest = actualWorkInfo.getConstraints().getRequiredNetworkRequest();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_TRUSTED)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
-        assertThat(networkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+        assertThat(actualWorkInfo.getConstraints().getRequiredNetworkType()).isEqualTo(
+                NetworkType.CONNECTED);
 
         long expectedDelay = TEST_NEXT_CHECK_IN_TIME_MILLIS - TEST_CURRENT_TIME_MILLIS;
         assertThat(actualWorkInfo.getInitialDelayMillis()).isEqualTo(expectedDelay);
diff --git a/apex/Android.bp b/apex/Android.bp
index 48251a4..1034377 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -59,6 +59,7 @@
         // result in a build failure due to inconsistent flags.
         package_prefixes: [
             "android.devicelock",
+            "com.android.devicelock.flags",
         ],
     },
 }
diff --git a/flags/Android.bp b/flags/Android.bp
new file mode 100644
index 0000000..ad63d9d
--- /dev/null
+++ b/flags/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2024 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+    name: "devicelock-aconfig-flags",
+    package: "com.android.devicelock.flags",
+    container: "com.android.devicelock",
+    exportable: true,
+    srcs: ["flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "devicelock-aconfig-flags-lib",
+    aconfig_declarations: "devicelock-aconfig-flags",
+    min_sdk_version: "UpsideDownCake",
+    apex_available: [
+        "com.android.devicelock",
+    ],
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    visibility: [
+        "//packages/modules/DeviceLock:__subpackages__",
+    ],
+}
+
+java_aconfig_library {
+    name: "devicelock-exported-aconfig-flags-lib",
+    aconfig_declarations: "devicelock-aconfig-flags",
+    min_sdk_version: "UpsideDownCake",
+    mode: "exported",
+    apex_available: [
+        "com.android.devicelock",
+    ],
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    visibility: [
+        "//packages/modules/DeviceLock:__subpackages__",
+    ],
+}
diff --git a/flags/flags.aconfig b/flags/flags.aconfig
new file mode 100644
index 0000000..45b97f1
--- /dev/null
+++ b/flags/flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.devicelock.flags"
+container: "com.android.devicelock"
+
+flag {
+    name: "clear_device_restrictions"
+    is_exported: true
+    namespace: "devicelock"
+    description: "Flag for API to clear device restrictions"
+    bug: "349177010"
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index 30cc6e4..b7c6a7d 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -42,7 +42,10 @@
     name: "framework-devicelock",
     srcs: [":framework-devicelock-sources"],
     defaults: ["framework-module-defaults"],
-    permitted_packages: ["android.devicelock"],
+    permitted_packages: [
+        "android.devicelock",
+        "com.android.devicelock.flags",
+    ],
     impl_library_visibility: ["//packages/modules/DeviceLock:__subpackages__"],
     apex_available: [
         "com.android.devicelock",
@@ -50,4 +53,5 @@
     sdk_version: "module_current",
     min_sdk_version: "UpsideDownCake",
     libs: ["framework-annotations-lib"],
+    static_libs: ["devicelock-aconfig-flags-lib"],
 }
diff --git a/framework/api/current.txt b/framework/api/current.txt
index 61b599b..3ff70e3 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -9,6 +9,7 @@
   }
 
   public final class DeviceLockManager {
+    method @FlaggedApi("com.android.devicelock.flags.clear_device_restrictions") @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_LOCK_STATE) public void clearDeviceRestrictions(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_LOCK_STATE) public void getDeviceId(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.devicelock.DeviceId,java.lang.Exception>);
     method public void getKioskApps(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.Map<java.lang.Integer,java.lang.String>,java.lang.Exception>);
     method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_LOCK_STATE) public void isDeviceLocked(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
diff --git a/framework/java/android/devicelock/DeviceLockManager.java b/framework/java/android/devicelock/DeviceLockManager.java
index 0d6aa6d..46aa33f 100644
--- a/framework/java/android/devicelock/DeviceLockManager.java
+++ b/framework/java/android/devicelock/DeviceLockManager.java
@@ -16,8 +16,11 @@
 
 package android.devicelock;
 
+import static com.android.devicelock.flags.Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS;
+
 import android.Manifest.permission;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresFeature;
@@ -101,10 +104,10 @@
 
         try {
             mService.lockDevice(
-                    new ILockUnlockDeviceCallback.Stub() {
+                    new IVoidResultCallback.Stub() {
                         @Override
-                        public void onDeviceLockedUnlocked() {
-                            executor.execute(() -> callback.onResult(null));
+                        public void onSuccess() {
+                            executor.execute(() -> callback.onResult(/* result= */ null));
                         }
 
                         @Override
@@ -131,10 +134,10 @@
 
         try {
             mService.unlockDevice(
-                    new ILockUnlockDeviceCallback.Stub() {
+                    new IVoidResultCallback.Stub() {
                         @Override
-                        public void onDeviceLockedUnlocked() {
-                            executor.execute(() -> callback.onResult(null));
+                        public void onSuccess() {
+                            executor.execute(() -> callback.onResult(/* result= */ null));
                         }
 
                         @Override
@@ -179,6 +182,65 @@
     }
 
     /**
+     * Clear device restrictions.
+     *
+     * <p>After a device determines that it's part of a program (e.g. financing) by checking in with
+     * the device lock backend, it will go though a provisioning flow and install a kiosk app.
+     *
+     * <p>At this point, the device is "restricted" and the creditor kiosk app is able to lock
+     * the device. For example, a creditor kiosk app in a financing use case may lock the device
+     * (using {@link #lockDevice}) if payments are missed and unlock (using {@link #unlockDevice})
+     * once they are resumed.
+     *
+     * <p>The Device Lock solution will also put in place some additional restrictions when a device
+     * is enrolled in the program, namely:
+     *
+     * <ul>
+     *     <li>Disable debugging features
+     *     ({@link android.os.UserManager#DISALLOW_DEBUGGING_FEATURES})
+     *     <li>Disable installing from unknown sources
+     *     ({@link android.os.UserManager#DISALLOW_INSTALL_UNKNOWN_SOURCES},
+     *     when configured in the backend)
+     *     <li>Disable outgoing calls
+     *     ({@link android.os.UserManager#DISALLOW_OUTGOING_CALLS},
+     *     when configured in the backend and the device is locked)
+     * </ul>
+     *
+     * <p>Once the program is completed (e.g. the device has been fully paid off), the kiosk app
+     * can use the {@link #clearDeviceRestrictions} API to lift the above restrictions.
+     *
+     * <p>At this point, the kiosk app has relinquished its ability to lock the device.
+     *
+     * @param executor the {@link Executor} on which to invoke the callback.
+     * @param callback this returns either success or an exception.
+     */
+    @RequiresPermission(permission.MANAGE_DEVICE_LOCK_STATE)
+    @FlaggedApi(FLAG_CLEAR_DEVICE_RESTRICTIONS)
+    public void clearDeviceRestrictions(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<Void, Exception> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            mService.clearDeviceRestrictions(
+                    new IVoidResultCallback.Stub() {
+                        @Override
+                        public void onSuccess() {
+                            executor.execute(() -> callback.onResult(/* result= */ null));
+                        }
+
+                        @Override
+                        public void onError(ParcelableException parcelableException) {
+                            callback.onError(parcelableException.getException());
+                        }
+                    }
+            );
+        } catch (RemoteException e) {
+            executor.execute(() -> callback.onError(new RuntimeException(e)));
+        }
+    }
+
+    /**
      * Get the device id.
      *
      * @param executor the {@link Executor} on which to invoke the callback.
diff --git a/framework/java/android/devicelock/IDeviceLockService.aidl b/framework/java/android/devicelock/IDeviceLockService.aidl
index 0e19fb9..90fb272 100644
--- a/framework/java/android/devicelock/IDeviceLockService.aidl
+++ b/framework/java/android/devicelock/IDeviceLockService.aidl
@@ -19,7 +19,7 @@
 import android.devicelock.IGetKioskAppsCallback;
 import android.devicelock.IGetDeviceIdCallback;
 import android.devicelock.IIsDeviceLockedCallback;
-import android.devicelock.ILockUnlockDeviceCallback;
+import android.devicelock.IVoidResultCallback;
 
 import android.os.RemoteCallback;
 
@@ -31,12 +31,12 @@
     /**
      * Asynchronously lock the device.
      */
-    void lockDevice(in ILockUnlockDeviceCallback callback);
+    void lockDevice(in IVoidResultCallback callback);
 
     /**
      * Asynchronously unlock the device.
      */
-    void unlockDevice(in ILockUnlockDeviceCallback callback);
+    void unlockDevice(in IVoidResultCallback callback);
 
     /**
      * Asynchronously retrieve the device lock status.
@@ -44,6 +44,11 @@
     void isDeviceLocked(in IIsDeviceLockedCallback callback);
 
     /**
+     * Asynchronously clear the device restrictions.
+     */
+    void clearDeviceRestrictions(in IVoidResultCallback callback);
+
+    /**
      * Asynchronously retrieve the device identifier.
      */
     void getDeviceId(in IGetDeviceIdCallback callback);
diff --git a/framework/java/android/devicelock/ILockUnlockDeviceCallback.aidl b/framework/java/android/devicelock/IVoidResultCallback.aidl
similarity index 85%
rename from framework/java/android/devicelock/ILockUnlockDeviceCallback.aidl
rename to framework/java/android/devicelock/IVoidResultCallback.aidl
index 6664ecb..036b3e9 100644
--- a/framework/java/android/devicelock/ILockUnlockDeviceCallback.aidl
+++ b/framework/java/android/devicelock/IVoidResultCallback.aidl
@@ -19,11 +19,11 @@
 import android.devicelock.ParcelableException;
 
 /**
-  * Callback for a lockDevice()/unlockDevice() request.
+  * Generic callback for requests not returning a value.
   * {@hide}
   */
-oneway interface ILockUnlockDeviceCallback {
-    void onDeviceLockedUnlocked();
+oneway interface IVoidResultCallback {
+    void onSuccess();
 
     void onError(in ParcelableException parcelableException);
 }
diff --git a/framework/java/android/devicelock/ParcelableException.java b/framework/java/android/devicelock/ParcelableException.java
index 750138c..1d787b3 100644
--- a/framework/java/android/devicelock/ParcelableException.java
+++ b/framework/java/android/devicelock/ParcelableException.java
@@ -30,10 +30,6 @@
         super(t);
     }
 
-    public ParcelableException(String message) {
-        super(message);
-    }
-
     private static Exception readFromParcel(Parcel in) {
         final String name = in.readString();
         final String msg = in.readString();
diff --git a/service/java/com/android/server/devicelock/DeviceLockControllerConnectorStub.java b/service/java/com/android/server/devicelock/DeviceLockControllerConnectorStub.java
index e0783ef..3e5d618 100644
--- a/service/java/com/android/server/devicelock/DeviceLockControllerConnectorStub.java
+++ b/service/java/com/android/server/devicelock/DeviceLockControllerConnectorStub.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.os.OutcomeReceiver;
 
+import com.android.devicelock.flags.Flags;
 import com.android.internal.annotations.GuardedBy;
 
 import java.lang.annotation.ElementType;
@@ -144,6 +145,10 @@
 
     @GuardedBy("this")
     private boolean setExceptionIfDeviceIsCleared(OutcomeReceiver<?, Exception> callback) {
+        if (Flags.clearDeviceRestrictions()) {
+            return false;
+        }
+
         if (mPseudoState == DevicePseudoState.CLEARED) {
             setException(callback, "Device has been cleared!");
 
diff --git a/service/java/com/android/server/devicelock/DeviceLockServiceImpl.java b/service/java/com/android/server/devicelock/DeviceLockServiceImpl.java
index 36f53d5..ee384df 100644
--- a/service/java/com/android/server/devicelock/DeviceLockServiceImpl.java
+++ b/service/java/com/android/server/devicelock/DeviceLockServiceImpl.java
@@ -51,7 +51,7 @@
 import android.devicelock.IGetDeviceIdCallback;
 import android.devicelock.IGetKioskAppsCallback;
 import android.devicelock.IIsDeviceLockedCallback;
-import android.devicelock.ILockUnlockDeviceCallback;
+import android.devicelock.IVoidResultCallback;
 import android.devicelock.ParcelableException;
 import android.net.NetworkPolicyManager;
 import android.net.Uri;
@@ -460,11 +460,11 @@
                 == PERMISSION_GRANTED;
     }
 
-    private void reportDeviceLockedUnlocked(@NonNull ILockUnlockDeviceCallback callback,
+    private void reportDeviceLockedUnlocked(@NonNull IVoidResultCallback callback,
             @Nullable Exception exception) {
         try {
             if (exception == null) {
-                callback.onDeviceLockedUnlocked();
+                callback.onSuccess();
             } else {
                 callback.onError(getParcelableException(exception));
             }
@@ -474,7 +474,7 @@
     }
 
     private OutcomeReceiver<Void, Exception> getLockUnlockOutcomeReceiver(
-            @NonNull ILockUnlockDeviceCallback callback, @NonNull String successMessage) {
+            @NonNull IVoidResultCallback callback, @NonNull String successMessage) {
         return new OutcomeReceiver<>() {
             @Override
             public void onResult(Void ignored) {
@@ -496,7 +496,7 @@
     }
 
     @Override
-    public void lockDevice(@NonNull ILockUnlockDeviceCallback callback) {
+    public void lockDevice(@NonNull IVoidResultCallback callback) {
         if (!checkCallerPermission()) {
             try {
                 callback.onError(new ParcelableException(new SecurityException()));
@@ -511,7 +511,7 @@
     }
 
     @Override
-    public void unlockDevice(@NonNull ILockUnlockDeviceCallback callback) {
+    public void unlockDevice(@NonNull IVoidResultCallback callback) {
         if (!checkCallerPermission()) {
             try {
                 callback.onError(new ParcelableException(new SecurityException()));
@@ -526,6 +526,45 @@
     }
 
     @Override
+    public void clearDeviceRestrictions(@NonNull IVoidResultCallback callback) {
+        if (!checkCallerPermission()) {
+            try {
+                callback.onError(new ParcelableException(new SecurityException()));
+            } catch (RemoteException e) {
+                Slog.e(TAG, "clearDeviceRestrictions() - Unable to send error to the callback", e);
+            }
+            return;
+        }
+
+        final UserHandle userHandle = Binder.getCallingUserHandle();
+
+        getDeviceLockControllerConnector(userHandle).clearDeviceRestrictions(
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(Void ignored) {
+                        Slog.i(TAG, "Device cleared ");
+
+                        try {
+                            callback.onSuccess();
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Unable to send result to the callback", e);
+                        }
+                    }
+
+                    @Override
+                    public void onError(Exception ex) {
+                        Slog.e(TAG, "Exception clearing device: ", ex);
+
+                        try {
+                            callback.onError(getParcelableException(ex));
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Unable to send error to the callback", e);
+                        }
+                    }
+                });
+    }
+
+    @Override
     public void isDeviceLocked(@NonNull IIsDeviceLockedCallback callback) {
         if (!checkCallerPermission()) {
             try {
@@ -573,7 +612,8 @@
     void getDeviceId(@NonNull IGetDeviceIdCallback callback, int deviceIdTypeBitmap) {
         try {
             if (deviceIdTypeBitmap < 0 || deviceIdTypeBitmap >= (1 << (LAST_DEVICE_ID_TYPE + 1))) {
-                callback.onError(new ParcelableException("Invalid device type"));
+                Exception exception = new Exception("Invalid device type");
+                callback.onError(new ParcelableException(exception));
                 return;
             }
         } catch (RemoteException e) {
@@ -620,7 +660,8 @@
                     //
                     // TODO(b/270392813): Send the device ID back to the callback with
                     //  UNSPECIFIED device ID type.
-                    callback.onError(new ParcelableException("Unable to get device id"));
+                    Exception exception = new Exception("Unable to get device id");
+                    callback.onError(new ParcelableException(exception));
                 } catch (RemoteException e) {
                     Slog.e(TAG, "getDeviceId() - Unable to send result to the callback", e);
                 }
@@ -985,21 +1026,37 @@
 
     @Override
     public void enableKioskKeepalive(String packageName, @NonNull RemoteCallback remoteCallback) {
+        if (!checkDeviceLockControllerPermission(remoteCallback)) {
+            return;
+        }
+
         enableKeepalive(true /* forKiosk */, packageName, remoteCallback);
     }
 
     @Override
     public void disableKioskKeepalive(@NonNull RemoteCallback remoteCallback) {
+        if (!checkDeviceLockControllerPermission(remoteCallback)) {
+            return;
+        }
+
         disableKeepalive(true /* forKiosk */, remoteCallback);
     }
 
     @Override
     public void enableControllerKeepalive(@NonNull RemoteCallback remoteCallback) {
+        if (!checkDeviceLockControllerPermission(remoteCallback)) {
+            return;
+        }
+
         enableKeepalive(false /* forKiosk */, mServiceInfo.packageName, remoteCallback);
     }
 
     @Override
     public void disableControllerKeepalive(@NonNull RemoteCallback remoteCallback) {
+        if (!checkDeviceLockControllerPermission(remoteCallback)) {
+            return;
+        }
+
         disableKeepalive(false /* forKiosk */, remoteCallback);
     }
 
@@ -1069,6 +1126,10 @@
 
     @Override
     public void setDeviceFinalized(boolean finalized, @NonNull RemoteCallback remoteCallback) {
+        if (!checkDeviceLockControllerPermission(remoteCallback)) {
+            return;
+        }
+
         mPersistentStore.scheduleWrite(finalized);
         UserHandle user = getCallingUserHandle();
         if (canDlcBeDisabledForFinalizedUser(user)) {
@@ -1084,6 +1145,10 @@
     @Override
     public void setPostNotificationsSystemFixed(boolean systemFixed,
             @NonNull RemoteCallback remoteCallback) {
+        if (!checkDeviceLockControllerPermission(remoteCallback)) {
+            return;
+        }
+
         final UserHandle userHandle = Binder.getCallingUserHandle();
         final PackageManager packageManager = mContext.getPackageManager();
         final int permissionFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
diff --git a/tests/cts/Android.bp b/tests/cts/Android.bp
index 5ab4c42..9892101 100644
--- a/tests/cts/Android.bp
+++ b/tests/cts/Android.bp
@@ -30,6 +30,7 @@
         "truth",
         "androidx.test.core",
         "compatibility-device-util-axt",
+        "devicelock-exported-aconfig-flags-lib",
     ],
     test_suites: [
         "general-tests",
diff --git a/tests/cts/src/com/android/cts/devicelock/DeviceLockManagerTest.java b/tests/cts/src/com/android/cts/devicelock/DeviceLockManagerTest.java
index d6bbb08..f3c12b7 100644
--- a/tests/cts/src/com/android/cts/devicelock/DeviceLockManagerTest.java
+++ b/tests/cts/src/com/android/cts/devicelock/DeviceLockManagerTest.java
@@ -26,6 +26,9 @@
 import android.os.Build;
 import android.os.OutcomeReceiver;
 import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArrayMap;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -34,9 +37,11 @@
 
 import com.android.compatibility.common.util.ApiTest;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.devicelock.flags.Flags;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -61,6 +66,9 @@
     private final DeviceLockManager mDeviceLockManager =
             mContext.getSystemService(DeviceLockManager.class);
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final int TIMEOUT = 1;
 
     private void addFinancedDeviceKioskRole() {
@@ -181,6 +189,26 @@
                 });
     }
 
+    private ListenableFuture<Void> getClearDeviceRestrictionsFuture() {
+        return CallbackToFutureAdapter.getFuture(
+                completer -> {
+                    mDeviceLockManager.clearDeviceRestrictions(mExecutorService,
+                            new OutcomeReceiver<>() {
+                                @Override
+                                public void onResult(Void result) {
+                                    completer.set(null);
+                                }
+
+                                @Override
+                                public void onError(Exception error) {
+                                    completer.setException(error);
+                                }
+                            });
+                    // Used only for debugging.
+                    return "clearDeviceRestrictions operation";
+                });
+    }
+
     @Test
     @ApiTest(apis = {"android.devicelock.DeviceLockManager#lockDevice"})
     public void lockDevicePermissionCheck() {
@@ -208,6 +236,20 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS)
+    @ApiTest(apis = {"android.devicelock.DeviceLockManager#clearDeviceRestrictions"})
+    public void clearDeviceRestrictionsPermissionCheck() {
+        ListenableFuture<Void> clearDeviceRestrictionsFuture = getClearDeviceRestrictionsFuture();
+
+        Exception clearDeviceRestrictionsResponseException =
+                assertThrows(
+                        ExecutionException.class,
+                        () -> clearDeviceRestrictionsFuture.get(TIMEOUT, TimeUnit.SECONDS));
+        assertThat(clearDeviceRestrictionsResponseException).hasCauseThat()
+                .isInstanceOf(SecurityException.class);
+    }
+
+    @Test
     @ApiTest(apis = {"android.devicelock.DeviceLockManager#isDeviceLocked"})
     public void isDeviceLockedPermissionCheck() {
         ListenableFuture<Boolean> isDeviceLockedFuture = getIsDeviceLockedFuture();
@@ -304,4 +346,19 @@
         Map<Integer, String> kioskAppsMap = getKioskAppsFuture().get(TIMEOUT, TimeUnit.SECONDS);
         assertThat(kioskAppsMap).isEmpty();
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS)
+    @ApiTest(apis = {"android.devicelock.DeviceLockManager#clearDeviceRestrictions"})
+    public void clearDeviceRestrictionsShouldSucceed()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        try {
+            addFinancedDeviceKioskRole();
+
+            // Clearing device restrictions should not throw an exception.
+            getClearDeviceRestrictionsFuture().get(TIMEOUT, TimeUnit.SECONDS);
+        } finally {
+            removeFinancedDeviceKioskRole();
+        }
+    }
 }
diff --git a/tests/unittests/Android.bp b/tests/unittests/Android.bp
index fdadbab..9c44d90 100644
--- a/tests/unittests/Android.bp
+++ b/tests/unittests/Android.bp
@@ -23,6 +23,7 @@
 
 android_robolectric_test {
     name: "DeviceLockUnitTests",
+    team: "trendy_team_android_go",
     srcs: [
         "src/**/*.java",
         ":framework-devicelock-sources",
@@ -30,8 +31,10 @@
     java_resource_dirs: ["config"],
     static_libs: [
         "service-devicelock",
+        "devicelock-aconfig-flags-lib",
         "androidx.test.core",
         "androidx.test.runner",
+        "flag-junit",
         "mockito-robolectric-prebuilt",
         "truth",
     ],
diff --git a/tests/unittests/src/com/android/server/devicelock/DeviceLockControllerConnectorStubTest.java b/tests/unittests/src/com/android/server/devicelock/DeviceLockControllerConnectorStubTest.java
index 8a82b74..402f815 100644
--- a/tests/unittests/src/com/android/server/devicelock/DeviceLockControllerConnectorStubTest.java
+++ b/tests/unittests/src/com/android/server/devicelock/DeviceLockControllerConnectorStubTest.java
@@ -21,12 +21,17 @@
 import static org.junit.Assert.assertThrows;
 
 import android.os.OutcomeReceiver;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.concurrent.futures.CallbackToFutureAdapter;
 
+import com.android.devicelock.flags.Flags;
+
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -43,6 +48,9 @@
     private DeviceLockControllerConnectorStub mDeviceLockControllerConnectorStub;
     private static final int TIMEOUT_SEC = 5;
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Before
     public void setup() throws Exception {
         mDeviceLockControllerConnectorStub = new DeviceLockControllerConnectorStub();
@@ -78,6 +86,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS)
     public void lockDevice_withClearedState_shouldThrowException()
             throws ExecutionException, InterruptedException, TimeoutException {
         // Given the device state is CLEARED
@@ -119,6 +128,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS)
     public void unlockDevice_withClearedState_shouldThrowException()
             throws ExecutionException, InterruptedException, TimeoutException {
         // Given the device state is CLEARED
@@ -160,6 +170,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS)
     public void clearDeviceRestrictions_withClearedState_shouldThrowException()
             throws ExecutionException, InterruptedException, TimeoutException {
         // Given the device state is CLEARED
@@ -202,6 +213,7 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_CLEAR_DEVICE_RESTRICTIONS)
     public void isDeviceLocked_withClearedState_shouldThrownException()
             throws ExecutionException, InterruptedException, TimeoutException {
         // Given the device state is CLEARED
diff --git a/tests/unittests/src/com/android/server/devicelock/DeviceLockServiceImplTest.java b/tests/unittests/src/com/android/server/devicelock/DeviceLockServiceImplTest.java
index f452a18..48ebeaf 100644
--- a/tests/unittests/src/com/android/server/devicelock/DeviceLockServiceImplTest.java
+++ b/tests/unittests/src/com/android/server/devicelock/DeviceLockServiceImplTest.java
@@ -398,6 +398,78 @@
                 .isEqualTo(COMPONENT_ENABLED_STATE_DISABLED);
     }
 
+    @Test
+    public void enableKioskKeepalive_withoutPermission_shouldFail() throws Exception {
+        mShadowApplication.denyPermissions(MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER);
+
+        AtomicBoolean succeeded = new AtomicBoolean(true);
+        mService.enableKioskKeepalive(mContext.getPackageName(), new RemoteCallback(result ->
+                succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT))));
+        waitUntilBgExecutorIdle();
+
+        assertThat(succeeded.get()).isFalse();
+    }
+
+    @Test
+    public void disableKioskKeepalive_withoutPermission_shouldFail() throws Exception {
+        mShadowApplication.denyPermissions(MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER);
+
+        AtomicBoolean succeeded = new AtomicBoolean(true);
+        mService.disableKioskKeepalive(new RemoteCallback(result ->
+                succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT))));
+        waitUntilBgExecutorIdle();
+
+        assertThat(succeeded.get()).isFalse();
+    }
+
+    @Test
+    public void enableControllerKeepalive_withoutPermission_shouldFail() throws Exception {
+        mShadowApplication.denyPermissions(MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER);
+
+        AtomicBoolean succeeded = new AtomicBoolean(true);
+        mService.enableControllerKeepalive(new RemoteCallback(result ->
+                succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT))));
+        waitUntilBgExecutorIdle();
+
+        assertThat(succeeded.get()).isFalse();
+    }
+
+    @Test
+    public void disableControllerKeepalive_withoutPermission_shouldFail() throws Exception {
+        mShadowApplication.denyPermissions(MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER);
+
+        AtomicBoolean succeeded = new AtomicBoolean(true);
+        mService.disableControllerKeepalive(new RemoteCallback(result ->
+                succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT))));
+        waitUntilBgExecutorIdle();
+
+        assertThat(succeeded.get()).isFalse();
+    }
+
+    @Test
+    public void setDeviceFinalized_withoutPermission_shouldFail() throws Exception {
+        mShadowApplication.denyPermissions(MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER);
+
+        AtomicBoolean succeeded = new AtomicBoolean(true);
+        mService.setDeviceFinalized(true, new RemoteCallback(result ->
+                succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT))));
+        waitUntilBgExecutorIdle();
+
+        assertThat(succeeded.get()).isFalse();
+    }
+
+    @Test
+    public void setPostNotificationsSystemFixed_withoutPermission_shouldFail() throws Exception {
+        mShadowApplication.denyPermissions(MANAGE_DEVICE_LOCK_SERVICE_FROM_CONTROLLER);
+
+        AtomicBoolean succeeded = new AtomicBoolean(true);
+        mService.setPostNotificationsSystemFixed(true, new RemoteCallback(result ->
+                succeeded.set(result.getBoolean(KEY_REMOTE_CALLBACK_RESULT))));
+        waitUntilBgExecutorIdle();
+
+        assertThat(succeeded.get()).isFalse();
+    }
+
     /**
      * Make the resolve info for the DLC package.
      */
diff --git a/tests/unittests/src/com/android/server/devicelock/ParcelableExceptionTest.java b/tests/unittests/src/com/android/server/devicelock/ParcelableExceptionTest.java
new file mode 100644
index 0000000..4a3e310
--- /dev/null
+++ b/tests/unittests/src/com/android/server/devicelock/ParcelableExceptionTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 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.devicelock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.devicelock.ParcelableException;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link android.devicelock.ParcelableException}.
+ */
+@RunWith(RobolectricTestRunner.class)
+public final class ParcelableExceptionTest {
+    private static final String EXCEPTION_MESSAGE = "TEST_EXCEPTION_MESSAGE";
+
+    @Test
+    public void parcelableExceptionShouldReturnOriginalException() {
+        Exception exception = new Exception(EXCEPTION_MESSAGE);
+        ParcelableException parcelableException = new ParcelableException(exception);
+
+        Exception cause = parcelableException.getException();
+
+        assertThat(cause).isNotNull();
+        assertThat(cause.getMessage()).isEqualTo(EXCEPTION_MESSAGE);
+    }
+
+    @Test
+    public void parcelableExceptionShouldParcelAndUnparcel() {
+        Parcel parcel = Parcel.obtain();
+        try {
+            Exception exception = new Exception(EXCEPTION_MESSAGE);
+            ParcelableException inParcelable = new ParcelableException(exception);
+            parcel.writeParcelable(inParcelable, 0);
+            parcel.setDataPosition(0);
+            ParcelableException outParcelable = parcel.readParcelable(
+                    ParcelableException.class.getClassLoader(), ParcelableException.class);
+            assertThat(outParcelable).isNotNull();
+            assertThat(inParcelable.getException().getMessage())
+                    .isEqualTo(outParcelable.getException().getMessage());
+        } finally {
+            parcel.recycle();
+        }
+    }
+}