Merge "Add escrow token API in DevicePolicyManager."
diff --git a/api/current.txt b/api/current.txt
index 5ecef52..a03ef38 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6200,6 +6200,7 @@
     method public void clearDeviceOwnerApp(java.lang.String);
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
+    method public boolean clearResetPasswordToken(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
     method public android.content.Intent createAdminSupportIntent(java.lang.String);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
@@ -6276,6 +6277,7 @@
     method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isProfileOwnerApp(java.lang.String);
     method public boolean isProvisioningAllowed(java.lang.String);
+    method public boolean isResetPasswordTokenActive(android.content.ComponentName);
     method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
     method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
     method public void lockNow();
@@ -6287,6 +6289,7 @@
     method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
+    method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int);
     method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
@@ -6335,6 +6338,7 @@
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
     method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
+    method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
     method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index e89c25c..406d51c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6405,6 +6405,7 @@
     method public void clearDeviceOwnerApp(java.lang.String);
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
+    method public boolean clearResetPasswordToken(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
     method public android.content.Intent createAdminSupportIntent(java.lang.String);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
@@ -6495,6 +6496,7 @@
     method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isProfileOwnerApp(java.lang.String);
     method public boolean isProvisioningAllowed(java.lang.String);
+    method public boolean isResetPasswordTokenActive(android.content.ComponentName);
     method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
     method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
     method public void lockNow();
@@ -6509,6 +6511,7 @@
     method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
+    method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int);
     method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
@@ -6559,6 +6562,7 @@
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
     method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
+    method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
     method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index b38e2b2..1be19ed 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6217,6 +6217,7 @@
     method public void clearDeviceOwnerApp(java.lang.String);
     method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String);
     method public void clearProfileOwner(android.content.ComponentName);
+    method public boolean clearResetPasswordToken(android.content.ComponentName);
     method public void clearUserRestriction(android.content.ComponentName, java.lang.String);
     method public android.content.Intent createAdminSupportIntent(java.lang.String);
     method public android.os.UserHandle createAndManageUser(android.content.ComponentName, java.lang.String, android.content.ComponentName, android.os.PersistableBundle, int);
@@ -6298,6 +6299,7 @@
     method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public boolean isProfileOwnerApp(java.lang.String);
     method public boolean isProvisioningAllowed(java.lang.String);
+    method public boolean isResetPasswordTokenActive(android.content.ComponentName);
     method public boolean isSecurityLoggingEnabled(android.content.ComponentName);
     method public boolean isUninstallBlocked(android.content.ComponentName, java.lang.String);
     method public void lockNow();
@@ -6309,6 +6311,7 @@
     method public boolean removeUser(android.content.ComponentName, android.os.UserHandle);
     method public boolean requestBugreport(android.content.ComponentName);
     method public boolean resetPassword(java.lang.String, int);
+    method public boolean resetPasswordWithToken(android.content.ComponentName, java.lang.String, byte[], int);
     method public java.util.List<android.app.admin.NetworkEvent> retrieveNetworkLogs(android.content.ComponentName, long);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
@@ -6357,6 +6360,7 @@
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
     method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
+    method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
     method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f1ccabe..0eb47a3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -28,6 +28,7 @@
 import android.annotation.WorkerThread;
 import android.app.Activity;
 import android.app.IServiceConnection;
+import android.app.KeyguardManager;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -2694,6 +2695,11 @@
      * Force a new device unlock password (the password needed to access the entire device, not for
      * individual accounts) on the user. This takes effect immediately.
      * <p>
+     * <em>For device owner and profile owners targeting SDK level
+     * {@link android.os.Build.VERSION_CODES#O} or above, this API is no longer available and will
+     * throw {@link SecurityException}. Please use the new API {@link #resetPasswordWithToken}
+     * instead. </em>
+     * <p>
      * <em>Note: This API has been limited as of {@link android.os.Build.VERSION_CODES#N} for
      * device admins that are not device owner and not profile owner.
      * The password can now only be changed if there is currently no password set.  Device owner
@@ -2740,6 +2746,127 @@
     }
 
     /**
+     * Called by a profile or device owner to provision a token which can later be used to reset the
+     * device lockscreen password (if called by device owner), or work challenge (if called by
+     * profile owner), via {@link #resetPasswordWithToken}.
+     * <p>
+     * If the user currently has a lockscreen password, the provisioned token will not be
+     * immediately usable; it only becomes active after the user performs a confirm credential
+     * operation, which can be triggered by {@link KeyguardManager#createConfirmDeviceCredentialIntent}.
+     * If the user has no lockscreen password, the token is activated immediately. In all cases,
+     * the active state of the current token can be checked by {@link #isResetPasswordTokenActive}.
+     * For security reasons, un-activated tokens are only stored in memory and will be lost once
+     * the device reboots. In this case a new token needs to be provisioned again.
+     * <p>
+     * Once provisioned and activated, the token will remain effective even if the user changes
+     * or clears the lockscreen password.
+     * <p>
+     * <em>This token is highly sensitive and should be treated at the same level as user
+     * credentials. In particular, NEVER store this token on device in plaintext, especially in
+     * Device-Encrypted storage if the token will be used to reset password on FBE devices before
+     * user unlocks.
+     * </em>
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param token a secure token a least 32-byte long, which must be generated by a
+     *        cryptographically strong random number generator.
+     * @return true if the operation is successful, false otherwise.
+     * @throws IllegalArgumentException if the supplied token is invalid.
+     * @throws SecurityException
+     */
+    public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
+        throwIfParentInstance("setResetPasswordToken");
+        if (mService != null) {
+            try {
+                return mService.setResetPasswordToken(admin, token);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by a profile or device owner to revoke the current password reset token.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @return true if the operation is successful, false otherwise.
+     */
+    public boolean clearResetPasswordToken(ComponentName admin) {
+        throwIfParentInstance("clearResetPasswordToken");
+        if (mService != null) {
+            try {
+                return mService.clearResetPasswordToken(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by a profile or device owner to check if the current reset password token is active.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @return true if the token is active, false otherwise.
+     * @throws IllegalStateException if no token has been set.
+     */
+    public boolean isResetPasswordTokenActive(ComponentName admin) {
+        throwIfParentInstance("isResetPasswordTokenActive");
+        if (mService != null) {
+            try {
+                return mService.isResetPasswordTokenActive(admin);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called by device or profile owner to force set a new device unlock password or a work profile
+     * challenge on current user. This takes effect immediately.
+     * <p>
+     * Unlike {@link #resetPassword}, this API can change the password even before the user or
+     * device is unlocked or decrypted. The supplied token must have been previously provisioned via
+     * {@link #setResetPasswordToken}, and in active state {@link #isResetPasswordTokenActive}.
+     * <p>
+     * The given password must be sufficient for the current password quality and length constraints
+     * as returned by {@link #getPasswordQuality(ComponentName)} and
+     * {@link #getPasswordMinimumLength(ComponentName)}; if it does not meet these constraints, then
+     * it will be rejected and false returned. Note that the password may be a stronger quality
+     * (containing alphanumeric characters when the requested quality is only numeric), in which
+     * case the currently active quality will be increased to match.
+     * <p>
+     * Calling with a null or empty password will clear any existing PIN, pattern or password if the
+     * current password constraints allow it.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @param password The new password for the user. Null or empty clears the password.
+     * @param token the password reset token previously provisioned by #setResetPasswordToken.
+     * @param flags May be 0 or combination of {@link #RESET_PASSWORD_REQUIRE_ENTRY} and
+     *            {@link #RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT}.
+     * @return Returns true if the password was applied, or false if it is not acceptable for the
+     *         current constraints.
+     * @throws SecurityException if the calling application does not own an active administrator
+     *             that uses {@link DeviceAdminInfo#USES_POLICY_RESET_PASSWORD}
+     * @throws IllegalStateException if the provided token is not valid.
+     * @throws IllegalArgumentException if the password does not meet system requirements.
+     */
+    public boolean resetPasswordWithToken(@NonNull ComponentName admin, String password,
+            byte[] token, int flags) {
+        throwIfParentInstance("resetPassword");
+        if (mService != null) {
+            try {
+                return mService.resetPasswordWithToken(admin, password, token, flags);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Called by an application that is administering the device to set the maximum time for user
      * activity until the device will lock. This limits the length that the user can set. It takes
      * effect immediately.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 79fe10e..c2f75c8 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -342,4 +342,9 @@
     long getLastSecurityLogRetrievalTime();
     long getLastBugReportRequestTime();
     long getLastNetworkLogRetrievalTime();
+
+    boolean setResetPasswordToken(in ComponentName admin, in byte[] token);
+    boolean clearResetPasswordToken(in ComponentName admin);
+    boolean isResetPasswordTokenActive(in ComponentName admin);
+    boolean resetPasswordWithToken(in ComponentName admin, String password, in byte[] token, int flags);
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2b2bb48..dd44aa0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -244,6 +244,8 @@
 
     private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
 
+    private static final String TAG_PASSWORD_TOKEN_HANDLE = "password-token";
+
     private static final int REQUEST_EXPIRE_PASSWORD = 5571;
 
     private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -506,6 +508,8 @@
         boolean mAdminBroadcastPending = false;
         PersistableBundle mInitBundle = null;
 
+        long mPasswordTokenHandle = 0;
+
         public DevicePolicyData(int userHandle) {
             mUserHandle = userHandle;
         }
@@ -2557,6 +2561,13 @@
                 out.endTag(null, TAG_INITIALIZATION_BUNDLE);
             }
 
+            if (policy.mPasswordTokenHandle != 0) {
+                out.startTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+                out.attribute(null, ATTR_VALUE,
+                        Long.toString(policy.mPasswordTokenHandle));
+                out.endTag(null, TAG_PASSWORD_TOKEN_HANDLE);
+            }
+
             out.endTag(null, "policies");
 
             out.endDocument();
@@ -2763,6 +2774,9 @@
                         m.symbols = Integer.parseInt(parser.getAttributeValue(null, "symbols"));
                         m.nonLetter = Integer.parseInt(parser.getAttributeValue(null, "nonletter"));
                     }
+                } else if (TAG_PASSWORD_TOKEN_HANDLE.equals(tag)) {
+                    policy.mPasswordTokenHandle = Long.parseLong(
+                            parser.getAttributeValue(null, ATTR_VALUE));
                 } else {
                     Slog.w(LOG_TAG, "Unknown tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -4074,12 +4088,8 @@
             mInjector.binderRestoreCallingIdentity(token);
         }
     }
-
     @Override
     public boolean resetPassword(String passwordOrNull, int flags) throws RemoteException {
-        if (!mHasFeature) {
-            return false;
-        }
         final int callingUid = mInjector.binderGetCallingUid();
         final int userHandle = mInjector.userHandleGetCallingUserId();
 
@@ -4090,15 +4100,18 @@
             enforceNotManagedProfile(userHandle, "clear the active password");
         }
 
-        int quality;
         synchronized (this) {
             // If caller has PO (or DO) it can change the password, so see if that's the case first.
             ActiveAdmin admin = getActiveAdminWithPolicyForUidLocked(
                     null, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, callingUid);
             final boolean preN;
             if (admin != null) {
-                preN = getTargetSdk(admin.info.getPackageName(),
-                        userHandle) <= android.os.Build.VERSION_CODES.M;
+                final int targetSdk = getTargetSdk(admin.info.getPackageName(), userHandle);
+                if (targetSdk >= Build.VERSION_CODES.O) {
+                    throw new SecurityException("resetPassword() is deprecated for DPC targeting O"
+                            + " or later");
+                }
+                preN = targetSdk <= android.os.Build.VERSION_CODES.M;
             } else {
                 // Otherwise, make sure the caller has any active admin with the right policy.
                 admin = getActiveAdminForCallerLocked(null,
@@ -4149,7 +4162,15 @@
                     return false;
                 }
             }
+        }
 
+        return resetPasswordInternal(password, 0, null, flags, callingUid, userHandle);
+    }
+
+    private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
+            int flags, int callingUid, int userHandle) {
+        int quality;
+        synchronized (this) {
             quality = getPasswordQuality(null, userHandle, /* parent */ false);
             if (quality == DevicePolicyManager.PASSWORD_QUALITY_MANAGED) {
                 quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -4239,11 +4260,20 @@
         // Don't do this with the lock held, because it is going to call
         // back in to the service.
         final long ident = mInjector.binderClearCallingIdentity();
+        final boolean result;
         try {
-            if (!TextUtils.isEmpty(password)) {
-                mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
+            if (token == null) {
+                if (!TextUtils.isEmpty(password)) {
+                    mLockPatternUtils.saveLockPassword(password, null, quality, userHandle);
+                } else {
+                    mLockPatternUtils.clearLock(null, userHandle);
+                }
+                result = true;
             } else {
-                mLockPatternUtils.clearLock(null, userHandle);
+                result = mLockPatternUtils.setLockCredentialWithToken(password,
+                        TextUtils.isEmpty(password) ? LockPatternUtils.CREDENTIAL_TYPE_NONE
+                                : LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
+                        tokenHandle, token, userHandle);
             }
             boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
             if (requireEntry) {
@@ -4260,8 +4290,7 @@
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
-
-        return true;
+        return result;
     }
 
     private boolean isLockScreenSecureUnchecked(int userId) {
@@ -9092,13 +9121,16 @@
             }
             final String deviceOwnerPackageName = mOwners.getDeviceOwnerComponent()
                     .getPackageName();
-            final String[] pkgs = mInjector.getPackageManager().getPackagesForUid(callerUid);
-
-            for (String pkg : pkgs) {
-                if (deviceOwnerPackageName.equals(pkg)) {
-                    return true;
+                try {
+                    String[] pkgs = mInjector.getIPackageManager().getPackagesForUid(callerUid);
+                    for (String pkg : pkgs) {
+                        if (deviceOwnerPackageName.equals(pkg)) {
+                            return true;
+                        }
+                    }
+                } catch (RemoteException e) {
+                    return false;
                 }
-            }
         }
 
         return false;
@@ -10621,4 +10653,98 @@
         enforceDeviceOwnerOrManageUsers();
         return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime;
     }
+
+    @Override
+    public boolean setResetPasswordToken(ComponentName admin, byte[] token) {
+        if (!mHasFeature) {
+            return false;
+        }
+        if (token == null || token.length < 32) {
+            throw new IllegalArgumentException("token must be at least 32-byte long");
+        }
+        synchronized (this) {
+            final int userHandle = mInjector.userHandleGetCallingUserId();
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            DevicePolicyData policy = getUserData(userHandle);
+            long ident = mInjector.binderClearCallingIdentity();
+            try {
+                if (policy.mPasswordTokenHandle != 0) {
+                    mLockPatternUtils.removeEscrowToken(policy.mPasswordTokenHandle, userHandle);
+                }
+
+                policy.mPasswordTokenHandle = mLockPatternUtils.addEscrowToken(token, userHandle);
+                saveSettingsLocked(userHandle);
+                return policy.mPasswordTokenHandle != 0;
+            } finally {
+                mInjector.binderRestoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public boolean clearResetPasswordToken(ComponentName admin) {
+        if (!mHasFeature) {
+            return false;
+        }
+        synchronized (this) {
+            final int userHandle = mInjector.userHandleGetCallingUserId();
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            DevicePolicyData policy = getUserData(userHandle);
+            if (policy.mPasswordTokenHandle != 0) {
+                long ident = mInjector.binderClearCallingIdentity();
+                try {
+                    boolean result = mLockPatternUtils.removeEscrowToken(
+                            policy.mPasswordTokenHandle, userHandle);
+                    policy.mPasswordTokenHandle = 0;
+                    saveSettingsLocked(userHandle);
+                    return result;
+                } finally {
+                    mInjector.binderRestoreCallingIdentity(ident);
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isResetPasswordTokenActive(ComponentName admin) {
+        synchronized (this) {
+            final int userHandle = mInjector.userHandleGetCallingUserId();
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            DevicePolicyData policy = getUserData(userHandle);
+            if (policy.mPasswordTokenHandle != 0) {
+                long ident = mInjector.binderClearCallingIdentity();
+                try {
+                    return mLockPatternUtils.isEscrowTokenActive(policy.mPasswordTokenHandle,
+                            userHandle);
+                } finally {
+                    mInjector.binderRestoreCallingIdentity(ident);
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean resetPasswordWithToken(ComponentName admin, String passwordOrNull, byte[] token,
+            int flags) {
+        Preconditions.checkNotNull(token);
+        synchronized (this) {
+            final int userHandle = mInjector.userHandleGetCallingUserId();
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            DevicePolicyData policy = getUserData(userHandle);
+            if (policy.mPasswordTokenHandle != 0) {
+                final String password = passwordOrNull != null ? passwordOrNull : "";
+                return resetPasswordInternal(password, policy.mPasswordTokenHandle, token,
+                        flags, mInjector.binderGetCallingUid(), userHandle);
+            } else {
+                Slog.w(LOG_TAG, "No saved token handle");
+            }
+        }
+        return false;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a186b59..23a1bb4 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -56,6 +56,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ParcelableString;
+import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.UserRestrictionsUtils;
@@ -3735,6 +3736,41 @@
                 dpm.getPermissionGrantState(admin1, app2, permission));
     }
 
+    public void testResetPasswordWithToken() throws Exception {
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        // test token validation
+        try {
+            dpm.setResetPasswordToken(admin1, new byte[31]);
+            fail("should not have accepted tokens too short");
+        } catch (IllegalArgumentException expected) {
+        }
+        // test adding a token
+        final byte[] token = new byte[32];
+        final long handle = 123456;
+        final String password = "password";
+        when(mContext.lockPatternUtils.addEscrowToken(eq(token), eq(UserHandle.USER_SYSTEM)))
+            .thenReturn(handle);
+        assertTrue(dpm.setResetPasswordToken(admin1, token));
+
+        // test password activation
+        when(mContext.lockPatternUtils.isEscrowTokenActive(eq(handle), eq(UserHandle.USER_SYSTEM)))
+            .thenReturn(true);
+        assertTrue(dpm.isResetPasswordTokenActive(admin1));
+
+        // test reset password with token
+        when(mContext.lockPatternUtils.setLockCredentialWithToken(eq(password),
+                eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD), eq(handle), eq(token),
+                eq(UserHandle.USER_SYSTEM)))
+                .thenReturn(true);
+        assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
+
+        // test removing a token
+        when(mContext.lockPatternUtils.removeEscrowToken(eq(handle), eq(UserHandle.USER_SYSTEM)))
+                .thenReturn(true);
+        assertTrue(dpm.clearResetPasswordToken(admin1));
+    }
+
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
         when(mContext.settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
                 userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index ed6779c..43e2610 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -174,6 +174,8 @@
                 anyInt(),
                 eq(UserHandle.getUserId(packageUid)));
 
+        doReturn(new String[] {admin.getPackageName()}).when(mMockContext.ipackageManager)
+            .getPackagesForUid(eq(packageUid));
         // Set up getPackageInfo().
         markPackageAsInstalled(admin.getPackageName(), ai, UserHandle.getUserId(packageUid));
     }