[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();
+ }
+ }
+}