Stop LockSettingsService from calling DevicePolicyManager directly

This is a violation of layering (LSS is considered a lower level
component than DPM) and source of deadlock due to lock inversion.
This change tries to remove most of the direct calls into DPM from
LSS. After this, there will only be a handful non-critical invocations
remaining:
1. DPM.reportPasswordChanged()
   This is always called on a handler thread so it's OK (LSS does not
   hold any hold while calling out). Consider this as a (asynchronous)
   broadcast.
2. DPMi.reportSeparateProfileChallengeChanged()
   This is now moved to the handler thread, similar to
   DPM.reportPasswordChanged().
3. DPMi.canUserHaveUntrustedCredentialReset()
   This is still a violation but it will soon be removed as we
   remove the caching of syhnthetic password alltogether (deprecating
   old resetPassword()). So I'll leave it for now.

Test: atest com.android.server.locksettings
Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest
Test: atest MixedManagedProfileOwnerTest#testResetPasswordWithToken
Test: atest MixedDeviceOwnerTest#testResetPasswordWithToken
Bug: 37090873
Bug: 141537958
Change-Id: Ie44cb418ab255bd016c5dd448674beabd362b74c
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index 8765760..b873be3 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -153,4 +153,11 @@
      * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
      */
     protected abstract DevicePolicyCache getDevicePolicyCache();
+
+    /**
+     * @return cached version of device state related to DPM that can be accessed without risking
+     * deadlocks.
+     * Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
+     */
+    protected abstract DeviceStateCache getDeviceStateCache();
 }
diff --git a/core/java/android/app/admin/DeviceStateCache.java b/core/java/android/app/admin/DeviceStateCache.java
new file mode 100644
index 0000000..7619aa2
--- /dev/null
+++ b/core/java/android/app/admin/DeviceStateCache.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 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 android.app.admin;
+
+import com.android.server.LocalServices;
+
+/**
+ * Stores a copy of the set of device state maintained by {@link DevicePolicyManager} which
+ * is not directly related to admin policies. This lives in its own class so that the state
+ * can be accessed from any place without risking dead locks.
+ *
+ * @hide
+ */
+public abstract class DeviceStateCache {
+    protected DeviceStateCache() {
+    }
+
+    /**
+     * @return the instance.
+     */
+    public static DeviceStateCache getInstance() {
+        final DevicePolicyManagerInternal dpmi =
+                LocalServices.getService(DevicePolicyManagerInternal.class);
+        return (dpmi != null) ? dpmi.getDeviceStateCache() : EmptyDeviceStateCache.INSTANCE;
+    }
+
+    /**
+     * See {@link DevicePolicyManager#isDeviceProvisioned}
+     */
+    public abstract boolean isDeviceProvisioned();
+
+    /**
+     * Empty implementation.
+     */
+    private static class EmptyDeviceStateCache extends DeviceStateCache {
+        private static final EmptyDeviceStateCache INSTANCE = new EmptyDeviceStateCache();
+
+        @Override
+        public boolean isDeviceProvisioned() {
+            return false;
+        }
+    }
+}
diff --git a/core/java/android/os/UserManagerInternal.java b/core/java/android/os/UserManagerInternal.java
index a5b71f6..59fb3d9 100644
--- a/core/java/android/os/UserManagerInternal.java
+++ b/core/java/android/os/UserManagerInternal.java
@@ -86,12 +86,22 @@
     public abstract void setDeviceManaged(boolean isManaged);
 
     /**
+     * Returns whether the device is managed by device owner.
+     */
+    public abstract boolean isDeviceManaged();
+
+    /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
      * whether the user is managed by profile owner.
      */
     public abstract void setUserManaged(int userId, boolean isManaged);
 
     /**
+     * whether a profile owner manages this user.
+     */
+    public abstract boolean isUserManaged(int userId);
+
+    /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to omit
      * restriction check, because DevicePolicyManager must always be able to set user icon
      * regardless of any restriction.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b7eca29..378d9eb 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -44,6 +44,7 @@
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
 import android.app.admin.PasswordMetrics;
 import android.app.backup.BackupManager;
 import android.app.trust.IStrongAuthTracker;
@@ -76,6 +77,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
@@ -429,6 +431,10 @@
             return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         }
 
+        public UserManagerInternal getUserManagerInternal() {
+            return LocalServices.getService(UserManagerInternal.class);
+        }
+
         /**
          * Return the {@link DevicePolicyManager} object.
          *
@@ -440,6 +446,10 @@
             return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         }
 
+        public DeviceStateCache getDeviceStateCache() {
+            return DeviceStateCache.getInstance();
+        }
+
         public KeyStore getKeyStore() {
             return KeyStore.getInstance();
         }
@@ -1023,11 +1033,16 @@
     }
 
     private void notifySeparateProfileChallengeChanged(int userId) {
-        final DevicePolicyManagerInternal dpmi = LocalServices.getService(
-                DevicePolicyManagerInternal.class);
-        if (dpmi != null) {
-            dpmi.reportSeparateProfileChallengeChanged(userId);
-        }
+        // LSS cannot call into DPM directly, otherwise it will cause deadlock.
+        // In this case, calling DPM on a handler thread is OK since DPM doesn't
+        // expect reportSeparateProfileChallengeChanged() to happen synchronously.
+        mHandler.post(() -> {
+            final DevicePolicyManagerInternal dpmi = LocalServices.getService(
+                    DevicePolicyManagerInternal.class);
+            if (dpmi != null) {
+                dpmi.reportSeparateProfileChallengeChanged(userId);
+            }
+        });
     }
 
     @Override
@@ -2038,7 +2053,6 @@
      * reporting the password changed.
      */
     private void notifyPasswordChanged(@UserIdInt int userId) {
-        // Same handler as notifyActivePasswordMetricsAvailable to ensure correct ordering
         mHandler.post(() -> {
             mInjector.getDevicePolicyManager().reportPasswordChanged(userId);
             LocalServices.getService(WindowManagerInternal.class).reportPasswordChanged(userId);
@@ -3026,45 +3040,43 @@
         pw.decreaseIndent();
     }
 
+    /**
+     * Cryptographically disable escrow token support for the current user, if the user is not
+     * managed (either user has a profile owner, or if device is managed). Do not disable
+     * if we are running an automotive build.
+     */
     private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
-        long ident = Binder.clearCallingIdentity();
-        try {
-            // Managed profile should have escrow enabled
-            if (mUserManager.getUserInfo(userId).isManagedProfile()) {
-                Slog.i(TAG, "Managed profile can have escrow token");
-                return;
-            }
-            DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
-            // Devices with Device Owner should have escrow enabled on all users.
-            if (dpm.getDeviceOwnerComponentOnAnyUser() != null) {
-                Slog.i(TAG, "Corp-owned device can have escrow token");
-                return;
-            }
-            // We could also have a profile owner on the given (non-managed) user for unicorn cases
-            if (dpm.getProfileOwnerAsUser(userId) != null) {
-                Slog.i(TAG, "User with profile owner can have escrow token");
-                return;
-            }
-            // If the device is yet to be provisioned (still in SUW), there is still
-            // a chance that Device Owner will be set on the device later, so postpone
-            // disabling escrow token for now.
-            if (!dpm.isDeviceProvisioned()) {
-                Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
-                return;
-            }
+        final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
 
-            // Escrow tokens are enabled on automotive builds.
-            if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-                return;
-            }
+        // Managed profile should have escrow enabled
+        if (userManagerInternal.isUserManaged(userId)) {
+            Slog.i(TAG, "Managed profile can have escrow token");
+            return;
+        }
 
-            // Disable escrow token permanently on all other device/user types.
-            Slog.i(TAG, "Disabling escrow token on user " + userId);
-            if (isSyntheticPasswordBasedCredentialLocked(userId)) {
-                mSpManager.destroyEscrowData(userId);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
+        // Devices with Device Owner should have escrow enabled on all users.
+        if (userManagerInternal.isDeviceManaged()) {
+            Slog.i(TAG, "Corp-owned device can have escrow token");
+            return;
+        }
+
+        // If the device is yet to be provisioned (still in SUW), there is still
+        // a chance that Device Owner will be set on the device later, so postpone
+        // disabling escrow token for now.
+        if (!mInjector.getDeviceStateCache().isDeviceProvisioned()) {
+            Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned");
+            return;
+        }
+
+        // Escrow tokens are enabled on automotive builds.
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            return;
+        }
+
+        // Disable escrow token permanently on all other device/user types.
+        Slog.i(TAG, "Disabling escrow token on user " + userId);
+        if (isSyntheticPasswordBasedCredentialLocked(userId)) {
+            mSpManager.destroyEscrowData(userId);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index dd1adb7..35e6a12 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3939,6 +3939,13 @@
         }
 
         @Override
+        public boolean isDeviceManaged() {
+            synchronized (mUsersLock) {
+                return mIsDeviceManaged;
+            }
+        }
+
+        @Override
         public void setUserManaged(@UserIdInt int userId, boolean isManaged) {
             synchronized (mUsersLock) {
                 mIsUserManaged.put(userId, isManaged);
@@ -3946,6 +3953,13 @@
         }
 
         @Override
+        public boolean isUserManaged(@UserIdInt int userId) {
+            synchronized (mUsersLock) {
+                return mIsUserManaged.get(userId);
+            }
+        }
+
+        @Override
         public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -4165,6 +4179,7 @@
             return restrictions != null && restrictions.getBoolean(restrictionKey);
         }
 
+        @Override
         public @Nullable UserInfo getUserInfo(@UserIdInt int userId) {
             UserData userData;
             synchronized (mUsersLock) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 478bc88..d0b57fb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -125,6 +125,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.SecurityLog;
@@ -506,6 +507,7 @@
     private final OverlayPackagesProvider mOverlayPackagesProvider;
 
     private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
+    private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
 
     /**
      * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
@@ -2308,6 +2310,9 @@
                 policy = new DevicePolicyData(userHandle);
                 mUserData.append(userHandle, policy);
                 loadSettingsLocked(policy, userHandle);
+                if (userHandle == UserHandle.USER_SYSTEM) {
+                    mStateCache.setDeviceProvisioned(policy.mUserSetupComplete);
+                }
             }
             return policy;
         }
@@ -8925,6 +8930,8 @@
             pw.println("Encryption Status: " + getEncryptionStatusName(getEncryptionStatus()));
             pw.println();
             mPolicyCache.dump(pw);
+            pw.println();
+            mStateCache.dump(pw);
         }
     }
 
@@ -11136,6 +11143,9 @@
                 DevicePolicyData policy = getUserData(userHandle);
                 if (!policy.mUserSetupComplete) {
                     policy.mUserSetupComplete = true;
+                    if (userHandle == UserHandle.USER_SYSTEM) {
+                        mStateCache.setDeviceProvisioned(true);
+                    }
                     synchronized (getLockObject()) {
                         saveSettingsLocked(userHandle);
                     }
@@ -11448,6 +11458,12 @@
         protected DevicePolicyCache getDevicePolicyCache() {
             return mPolicyCache;
         }
+
+        @Override
+        protected DeviceStateCache getDeviceStateCache() {
+            return mStateCache;
+        }
+
     }
 
     private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -12988,6 +13004,7 @@
                 Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0;
         DevicePolicyData policy = getUserData(userId);
         policy.mUserSetupComplete = isUserCompleted;
+        mStateCache.setDeviceProvisioned(isUserCompleted);
         synchronized (getLockObject()) {
             saveSettingsLocked(userId);
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
new file mode 100644
index 0000000..c3cb9b0
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 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.devicepolicy;
+
+import android.app.admin.DeviceStateCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * Implementation of {@link DeviceStateCache}, to which {@link DevicePolicyManagerService} pushes
+ * device state.
+ *
+ */
+public class DeviceStateCacheImpl extends DeviceStateCache {
+    /**
+     * Lock object. For simplicity we just always use this as the lock. We could use each object
+     * as a lock object to make it more fine-grained, but that'd make copy-paste error-prone.
+     */
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mIsDeviceProvisioned = false;
+
+    @Override
+    public boolean isDeviceProvisioned() {
+        return mIsDeviceProvisioned;
+    }
+
+    /** Update the device provisioned flag for USER_SYSTEM */
+    public void setDeviceProvisioned(boolean provisioned) {
+        synchronized (mLock) {
+            mIsDeviceProvisioned = provisioned;
+        }
+    }
+
+    /** Dump content */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("Device state cache:");
+        pw.increaseIndent();
+        pw.println("Device provisioned: " + mIsDeviceProvisioned);
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 1f5ebe4..7cece1f 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -29,6 +29,7 @@
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.DeviceStateCache;
 import android.app.trust.TrustManager;
 import android.content.ComponentName;
 import android.content.pm.UserInfo;
@@ -37,6 +38,7 @@
 import android.os.IProgressListener;
 import android.os.RemoteException;
 import android.os.UserManager;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.security.KeyStore;
@@ -91,6 +93,8 @@
     FakeGsiService mGsiService;
     PasswordSlotManagerTestable mPasswordSlotManager;
     RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+    UserManagerInternal mUserManagerInternal;
+    DeviceStateCache mDeviceStateCache;
     protected boolean mHasSecureLockScreen;
 
     @Override
@@ -108,6 +112,8 @@
         mGsiService = new FakeGsiService();
         mPasswordSlotManager = new PasswordSlotManagerTestable();
         mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class);
+        mUserManagerInternal = mock(UserManagerInternal.class);
+        mDeviceStateCache = mock(DeviceStateCache.class);
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -144,7 +150,8 @@
         mAuthSecretService = mock(IAuthSecret.class);
         mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage,
                 mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
-                mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager);
+                mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager,
+                mUserManagerInternal, mDeviceStateCache);
         when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
         mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
         installChildProfile(MANAGED_PROFILE_USER_ID);
@@ -172,6 +179,9 @@
         // Adding a fake Device Owner app which will enable escrow token support in LSS.
         when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
                 new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
+        when(mUserManagerInternal.isDeviceManaged()).thenReturn(true);
+        when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
+
         mLocalService = LocalServices.getService(LockSettingsInternal.class);
     }
 
@@ -184,6 +194,7 @@
         when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
         when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
         when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+        when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
         return userInfo;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 5c67d04..65d6f45 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -19,12 +19,14 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.IActivityManager;
+import android.app.admin.DeviceStateCache;
 import android.content.Context;
 import android.hardware.authsecret.V1_0.IAuthSecret;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.security.KeyStore;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
@@ -44,15 +46,16 @@
         private LockPatternUtils mLockPatternUtils;
         private IStorageManager mStorageManager;
         private SyntheticPasswordManager mSpManager;
-        private IAuthSecret mAuthSecretService;
         private FakeGsiService mGsiService;
         private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+        private UserManagerInternal mUserManagerInternal;
+        private DeviceStateCache mDeviceStateCache;
 
         public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
                 IActivityManager activityManager, LockPatternUtils lockPatternUtils,
                 IStorageManager storageManager, SyntheticPasswordManager spManager,
-                IAuthSecret authSecretService, FakeGsiService gsiService,
-                RecoverableKeyStoreManager recoverableKeyStoreManager) {
+                FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+                UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
             super(context);
             mLockSettingsStorage = storage;
             mKeyStore = keyStore;
@@ -62,6 +65,8 @@
             mSpManager = spManager;
             mGsiService = gsiService;
             mRecoverableKeyStoreManager = recoverableKeyStoreManager;
+            mUserManagerInternal = userManagerInternal;
+            mDeviceStateCache = deviceStateCache;
         }
 
         @Override
@@ -93,6 +98,10 @@
         public LockPatternUtils getLockPatternUtils() {
             return mLockPatternUtils;
         }
+        @Override
+        public DeviceStateCache getDeviceStateCache() {
+            return mDeviceStateCache;
+        }
 
         @Override
         public KeyStore getKeyStore() {
@@ -110,6 +119,11 @@
         }
 
         @Override
+        public UserManagerInternal getUserManagerInternal() {
+            return mUserManagerInternal;
+        }
+
+        @Override
         public boolean hasEnrolledBiometrics(int userId) {
             return false;
         }
@@ -134,10 +148,11 @@
             LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
             IStorageManager storageManager, IActivityManager mActivityManager,
             SyntheticPasswordManager spManager, IAuthSecret authSecretService,
-            FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager) {
+            FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager,
+            UserManagerInternal userManagerInternal, DeviceStateCache deviceStateCache) {
         super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
-                storageManager, spManager, authSecretService, gsiService,
-                recoverableKeyStoreManager));
+                storageManager, spManager, gsiService,
+                recoverableKeyStoreManager, userManagerInternal, deviceStateCache));
         mGateKeeperService = gatekeeper;
         mAuthSecretService = authSecretService;
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 127cf49..0776589 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.admin.PasswordMetrics;
 import android.os.RemoteException;
@@ -468,6 +469,18 @@
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
+    public void testEscrowTokenCannotBeActivatedOnUnmanagedUser() {
+        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        when(mUserManagerInternal.isDeviceManaged()).thenReturn(false);
+        when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
+        when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
+
+        try {
+            mLocalService.addEscrowToken(token, PRIMARY_USER_ID, null);
+            fail("Escrow token should not be possible on unmanaged device");
+        } catch (SecurityException expected) { }
+    }
+
     public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
         final byte[] password = "password".getBytes();
         final byte[] pattern = "123654".getBytes();