Add weak escrow token APIs

Bug: 206485383
Test: atest LockPatternUtilsTest WeakEscrowTokenTests
Change-Id: I24cdf9af6c8f29db03ef214a1a0f8effad394264
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c9b8ba2..06b2b2b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -176,6 +176,7 @@
     field public static final String MANAGE_USB = "android.permission.MANAGE_USB";
     field public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
     field public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission.MANAGE_USER_OEM_UNLOCK_STATE";
+    field public static final String MANAGE_WEAK_ESCROW_TOKEN = "android.permission.MANAGE_WEAK_ESCROW_TOKEN";
     field public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
     field public static final String MANAGE_WIFI_COUNTRY_CODE = "android.permission.MANAGE_WIFI_COUNTRY_CODE";
     field public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission.MARK_DEVICE_ORGANIZATION_OWNED";
@@ -757,18 +758,32 @@
   }
 
   public class KeyguardManager {
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public long addWeakEscrowToken(@NonNull byte[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenActivatedListener);
     method public android.content.Intent createConfirmFactoryResetCredentialIntent(CharSequence, CharSequence, CharSequence);
     method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public int getMinLockLength(boolean, int);
     method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public boolean getPrivateNotificationsAllowed();
     method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean isValidLockPasswordComplexity(int, @NonNull byte[], int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenActive(long, @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean isWeakEscrowTokenValid(long, @NonNull byte[], @NonNull android.os.UserHandle);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean registerWeakEscrowTokenRemovedListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean removeWeakEscrowToken(long, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.SHOW_KEYGUARD_MESSAGE) public void requestDismissKeyguard(@NonNull android.app.Activity, @Nullable CharSequence, @Nullable android.app.KeyguardManager.KeyguardDismissCallback);
     method @RequiresPermission("android.permission.SET_INITIAL_LOCK") public boolean setLock(int, @NonNull byte[], int);
     method @RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS) public void setPrivateNotificationsAllowed(boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN) public boolean unregisterWeakEscrowTokenRemovedListener(@NonNull android.app.KeyguardManager.WeakEscrowTokenRemovedListener);
     field public static final int PASSWORD = 0; // 0x0
     field public static final int PATTERN = 2; // 0x2
     field public static final int PIN = 1; // 0x1
   }
 
+  public static interface KeyguardManager.WeakEscrowTokenActivatedListener {
+    method public void onWeakEscrowTokenActivated(long, @NonNull android.os.UserHandle);
+  }
+
+  public static interface KeyguardManager.WeakEscrowTokenRemovedListener {
+    method public void onWeakEscrowTokenRemoved(long, @NonNull android.os.UserHandle);
+  }
+
   public class LocaleManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES) public android.os.LocaleList getApplicationLocales(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void setApplicationLocales(@NonNull String, @NonNull android.os.LocaleList);
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index dc71a32..14afd0f 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,8 +41,10 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.persistentdata.IPersistentDataBlockService;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.view.IOnKeyguardExitResult;
 import android.view.IWindowManager;
@@ -49,6 +52,9 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.util.Preconditions;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternView;
 import com.android.internal.widget.LockscreenCredential;
@@ -57,6 +63,8 @@
 import java.nio.charset.Charset;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
 
 /**
  * Class that can be used to lock and unlock the keyguard. The
@@ -69,10 +77,13 @@
     private static final String TAG = "KeyguardManager";
 
     private final Context mContext;
+    private final LockPatternUtils mLockPatternUtils;
     private final IWindowManager mWM;
     private final IActivityManager mAm;
     private final ITrustManager mTrustManager;
     private final INotificationManager mNotificationManager;
+    private final ArrayMap<WeakEscrowTokenRemovedListener, IWeakEscrowTokenRemovedListener>
+            mListeners = new ArrayMap<>();
 
     /**
      * Intent used to prompt user for device credentials.
@@ -455,8 +466,42 @@
         public void onDismissCancelled() { }
     }
 
+    /**
+     * Callback passed to
+     * {@link KeyguardManager#addWeakEscrowToken}
+     * to notify caller of state change.
+     * @hide
+     */
+    @SystemApi
+    public interface WeakEscrowTokenActivatedListener {
+        /**
+         * The method to be called when the token is activated.
+         * @param handle 64 bit handle corresponding to the escrow token
+         * @param user user for whom the weak escrow token has been added
+         */
+        void onWeakEscrowTokenActivated(long handle, @NonNull UserHandle user);
+    }
+
+    /**
+     * Listener passed to
+     * {@link KeyguardManager#registerWeakEscrowTokenRemovedListener} and
+     * {@link KeyguardManager#unregisterWeakEscrowTokenRemovedListener}
+     * to notify caller of an weak escrow token has been removed.
+     * @hide
+     */
+    @SystemApi
+    public interface WeakEscrowTokenRemovedListener {
+        /**
+         * The method to be called when the token is removed.
+         * @param handle 64 bit handle corresponding to the escrow token
+         * @param user user for whom the escrow token has been added
+         */
+        void onWeakEscrowTokenRemoved(long handle, @NonNull UserHandle user);
+    }
+
     KeyguardManager(Context context) throws ServiceNotFoundException {
         mContext = context;
+        mLockPatternUtils = new LockPatternUtils(context);
         mWM = WindowManagerGlobal.getWindowManagerService();
         mAm = ActivityManager.getService();
         mTrustManager = ITrustManager.Stub.asInterface(
@@ -785,7 +830,6 @@
             return false;
         }
 
-        LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
         int userId = mContext.getUserId();
         if (isDeviceSecure(userId)) {
             Log.e(TAG, "Password already set, rejecting call to setLock");
@@ -799,7 +843,7 @@
         try {
             LockscreenCredential credential = createLockscreenCredential(
                     lockType, password);
-            success = lockPatternUtils.setLockCredential(
+            success = mLockPatternUtils.setLockCredential(
                     credential,
                     /* savedPassword= */ LockscreenCredential.createNone(),
                     userId);
@@ -813,6 +857,150 @@
     }
 
     /**
+     * Create a weak escrow token for the current user, which can later be used to unlock FBE
+     * or change user password.
+     *
+     * After adding, if the user currently  has a secure lockscreen, they will need to perform a
+     * confirm credential operation in order to activate the token for future use. If the user
+     * has no secure lockscreen, then the token is activated immediately.
+     *
+     * If the user changes or removes the lockscreen password, any activated weak escrow token will
+     * be removed.
+     *
+     * @return a unique 64-bit token handle which is needed to refer to this token later.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public long addWeakEscrowToken(@NonNull byte[] token, @NonNull UserHandle user,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WeakEscrowTokenActivatedListener listener) {
+        Objects.requireNonNull(token, "Token cannot be null.");
+        Objects.requireNonNull(user, "User cannot be null.");
+        Objects.requireNonNull(executor, "Executor cannot be null.");
+        Objects.requireNonNull(listener, "Listener cannot be null.");
+        int userId = user.getIdentifier();
+        IWeakEscrowTokenActivatedListener internalListener =
+                new IWeakEscrowTokenActivatedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenActivated(long handle, int userId) {
+                UserHandle user = UserHandle.of(userId);
+                final long restoreToken = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> listener.onWeakEscrowTokenActivated(handle, user));
+                } finally {
+                    Binder.restoreCallingIdentity(restoreToken);
+                }
+                Log.i(TAG, "Weak escrow token activated.");
+            }
+        };
+        return mLockPatternUtils.addWeakEscrowToken(token, userId, internalListener);
+    }
+
+    /**
+     * Remove a weak escrow token.
+     *
+     * @return true if the given handle refers to a valid weak token previously returned from
+     * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean removeWeakEscrowToken(long handle, @NonNull UserHandle user) {
+        Objects.requireNonNull(user, "User cannot be null.");
+        return mLockPatternUtils.removeWeakEscrowToken(handle, user.getIdentifier());
+    }
+
+    /**
+     * Check if the given weak escrow token is active or not.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean isWeakEscrowTokenActive(long handle, @NonNull UserHandle user) {
+        Objects.requireNonNull(user, "User cannot be null.");
+        return mLockPatternUtils.isWeakEscrowTokenActive(handle, user.getIdentifier());
+    }
+
+    /**
+     * Check if the given weak escrow token is validate.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean isWeakEscrowTokenValid(long handle, @NonNull byte[] token,
+            @NonNull UserHandle user) {
+        Objects.requireNonNull(token, "Token cannot be null.");
+        Objects.requireNonNull(user, "User cannot be null.");
+        return mLockPatternUtils.isWeakEscrowTokenValid(handle, token, user.getIdentifier());
+    }
+
+    /**
+     * Register the given WeakEscrowTokenRemovedListener.
+     *
+     * @return true if the listener is registered successfully, return false otherwise.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean registerWeakEscrowTokenRemovedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull WeakEscrowTokenRemovedListener listener) {
+        Objects.requireNonNull(listener, "Listener cannot be null.");
+        Objects.requireNonNull(executor, "Executor cannot be null.");
+        Preconditions.checkArgument(!mListeners.containsKey(listener),
+                "Listener already registered: %s", listener);
+        IWeakEscrowTokenRemovedListener internalListener =
+                new IWeakEscrowTokenRemovedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenRemoved(long handle, int userId) {
+                UserHandle user = UserHandle.of(userId);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> listener.onWeakEscrowTokenRemoved(handle, user));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        };
+        if (mLockPatternUtils.registerWeakEscrowTokenRemovedListener(internalListener)) {
+            mListeners.put(listener, internalListener);
+            return true;
+        } else {
+            Log.e(TAG, "Listener failed to register");
+            return false;
+        }
+    }
+
+    /**
+     * Unregister the given WeakEscrowTokenRemovedListener.
+     *
+     * @return true if the listener is unregistered successfully, return false otherwise.
+     * @hide
+     */
+    @RequiresFeature(PackageManager.FEATURE_AUTOMOTIVE)
+    @RequiresPermission(Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN)
+    @SystemApi
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            @NonNull WeakEscrowTokenRemovedListener listener) {
+        Objects.requireNonNull(listener, "Listener cannot be null.");
+        IWeakEscrowTokenRemovedListener internalListener = mListeners.get(listener);
+        Preconditions.checkArgument(internalListener != null, "Listener was not registered");
+        if (mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(internalListener)) {
+            mListeners.remove(listener);
+            return true;
+        } else {
+            Log.e(TAG, "Listener failed to unregister.");
+            return false;
+        }
+    }
+
+    /**
      * Set the lockscreen password to {@code newPassword} after validating the current password
      * against {@code currentPassword}.
      * <p>If no password is currently set, {@code currentPassword} should be set to {@code null}.
@@ -832,13 +1020,12 @@
     })
     public boolean setLock(@LockTypes int newLockType, @Nullable byte[] newPassword,
             @LockTypes int currentLockType, @Nullable byte[] currentPassword) {
-        final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
         final int userId = mContext.getUserId();
         LockscreenCredential currentCredential = createLockscreenCredential(
                 currentLockType, currentPassword);
         LockscreenCredential newCredential = createLockscreenCredential(
                 newLockType, newPassword);
-        return lockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
+        return mLockPatternUtils.setLockCredential(newCredential, currentCredential, userId);
     }
 
     /**
@@ -857,10 +1044,9 @@
             Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE
     })
     public boolean checkLock(@LockTypes int lockType, @Nullable byte[] password) {
-        final LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
         final LockscreenCredential credential = createLockscreenCredential(
                 lockType, password);
-        final VerifyCredentialResponse response = lockPatternUtils.verifyCredential(
+        final VerifyCredentialResponse response = mLockPatternUtils.verifyCredential(
                 credential, mContext.getUserId(), /* flags= */ 0);
         if (response == null) {
             return false;
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index d16d9c6..db4bc2c 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -24,6 +24,8 @@
 import android.security.keystore.recovery.KeyChainProtectionParams;
 import android.security.keystore.recovery.RecoveryCertPath;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
 
@@ -96,4 +98,10 @@
     boolean tryUnlockWithCachedUnifiedChallenge(int userId);
     void removeCachedUnifiedChallenge(int userId);
     void updateEncryptionPassword(int type, in byte[] password);
+    boolean registerWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
+    boolean unregisterWeakEscrowTokenRemovedListener(in IWeakEscrowTokenRemovedListener listener);
+    long addWeakEscrowToken(in byte[] token, int userId, in IWeakEscrowTokenActivatedListener callback);
+    boolean removeWeakEscrowToken(long handle, int userId);
+    boolean isWeakEscrowTokenActive(long handle, int userId);
+    boolean isWeakEscrowTokenValid(long handle, in byte[] token, int userId);
 }
diff --git a/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl
new file mode 100644
index 0000000..9c8d9d6
--- /dev/null
+++ b/core/java/com/android/internal/widget/IWeakEscrowTokenActivatedListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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.internal.widget;
+
+/** @hide */
+oneway interface IWeakEscrowTokenActivatedListener {
+    void onWeakEscrowTokenActivated(long handle, int userId);
+}
diff --git a/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl
new file mode 100644
index 0000000..7018048
--- /dev/null
+++ b/core/java/com/android/internal/widget/IWeakEscrowTokenRemovedListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 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.internal.widget;
+
+/** @hide */
+oneway interface IWeakEscrowTokenRemovedListener {
+    void onWeakEscrowTokenRemoved(long handle, int userId);
+}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 5a03277..f91776e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1236,6 +1236,28 @@
         }
     }
 
+    /** Register the given WeakEscrowTokenRemovedListener. */
+    public boolean registerWeakEscrowTokenRemovedListener(
+            @NonNull final IWeakEscrowTokenRemovedListener listener) {
+        try {
+            return getLockSettings().registerWeakEscrowTokenRemovedListener(listener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Unregister the given WeakEscrowTokenRemovedListener. */
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            @NonNull final IWeakEscrowTokenRemovedListener listener) {
+        try {
+            return getLockSettings().unregisterWeakEscrowTokenRemovedListener(listener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not register WeakEscrowTokenRemovedListener.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
         try {
             getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
@@ -1355,15 +1377,38 @@
     }
 
     /**
+     * Create a weak escrow token for the current user, which can later be used to unlock FBE
+     * or change user password.
+     *
+     * After adding, if the user currently has lockscreen password, they will need to perform a
+     * confirm credential operation in order to activate the token for future use. If the user
+     * has no secure lockscreen, then the token is activated immediately.
+     *
+     * If the user changes or removes lockscreen password, activated weak escrow tokens will be
+     * removed.
+     *
+     * @return a unique 64-bit token handle which is needed to refer to this token later.
+     */
+    public long addWeakEscrowToken(byte[] token, int userId,
+            @NonNull IWeakEscrowTokenActivatedListener callback) {
+        try {
+            return getLockSettings().addWeakEscrowToken(token, userId, callback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not add weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Callback interface to notify when an added escrow token has been activated.
      */
     public interface EscrowTokenStateChangeCallback {
         /**
          * The method to be called when the token is activated.
          * @param handle 64 bit handle corresponding to the escrow token
-         * @param userid user for whom the escrow token has been added
+         * @param userId user for whom the escrow token has been added
          */
-        void onEscrowTokenActivated(long handle, int userid);
+        void onEscrowTokenActivated(long handle, int userId);
     }
 
     /**
@@ -1379,6 +1424,21 @@
     }
 
     /**
+     * Remove a weak escrow token.
+     *
+     * @return true if the given handle refers to a valid weak token previously returned from
+     * {@link #addWeakEscrowToken}, whether it's active or not. return false otherwise.
+     */
+    public boolean removeWeakEscrowToken(long handle, int userId) {
+        try {
+            return getLockSettings().removeWeakEscrowToken(handle, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not remove the weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Check if the given escrow token is active or not. Only active token can be used to call
      * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
      *
@@ -1389,6 +1449,29 @@
     }
 
     /**
+     * Check if the given weak escrow token is active or not. Only active token can be used to call
+     * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken}
+     */
+    public boolean isWeakEscrowTokenActive(long handle, int userId) {
+        try {
+            return getLockSettings().isWeakEscrowTokenActive(handle, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not check the weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Check if the given weak escrow token is valid. */
+    public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
+        try {
+            return getLockSettings().isWeakEscrowTokenValid(handle, token, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not validate the weak token.");
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Change a user's lock credential with a pre-configured escrow token.
      *
      * <p>This method is only available to code running in the system server process itself.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4c36732..1865d75 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5294,6 +5294,12 @@
     <permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission
+         is not available to third party applications.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to listen to trust changes.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.TRUST_LISTENER"
diff --git a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
index 50e8474..b659f37 100644
--- a/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/LockPatternUtilsTest.java
@@ -21,13 +21,16 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
+import android.os.RemoteException;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
@@ -38,12 +41,16 @@
 
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.nio.charset.StandardCharsets;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class LockPatternUtilsTest {
@@ -102,4 +109,84 @@
         configureTest(false, true, 0);
         assertFalse(mLockPatternUtils.isLockScreenDisabled(DEMO_USER_ID));
     }
+
+    @Test
+    public void testAddWeakEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        int testUserId = 10;
+        IWeakEscrowTokenActivatedListener listener = createWeakEscrowTokenListener();
+        mLockPatternUtils.addWeakEscrowToken(testToken, testUserId, listener);
+        verify(ils).addWeakEscrowToken(eq(testToken), eq(testUserId), eq(listener));
+    }
+
+    @Test
+    public void testRegisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.registerWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).registerWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testUnregisterWeakEscrowTokenRemovedListener() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        IWeakEscrowTokenRemovedListener testListener = createTestAutoEscrowTokenRemovedListener();
+        mLockPatternUtils.unregisterWeakEscrowTokenRemovedListener(testListener);
+        verify(ils).unregisterWeakEscrowTokenRemovedListener(eq(testListener));
+    }
+
+    @Test
+    public void testRemoveAutoEscrowToken() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.removeWeakEscrowToken(testHandle, testUserId);
+        verify(ils).removeWeakEscrowToken(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenActive() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenActive(testHandle, testUserId);
+        verify(ils).isWeakEscrowTokenActive(eq(testHandle), eq(testUserId));
+    }
+
+    @Test
+    public void testIsAutoEscrowTokenValid() throws RemoteException {
+        ILockSettings ils = createTestLockSettings();
+        int testUserId = 10;
+        byte[] testToken = "test_token".getBytes(StandardCharsets.UTF_8);
+        long testHandle = 100L;
+        mLockPatternUtils.isWeakEscrowTokenValid(testHandle, testToken, testUserId);
+        verify(ils).isWeakEscrowTokenValid(eq(testHandle), eq(testToken), eq(testUserId));
+    }
+
+    private ILockSettings createTestLockSettings() {
+        final Context context = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mLockPatternUtils = spy(new LockPatternUtils(context));
+        final ILockSettings ils = Mockito.mock(ILockSettings.class);
+        when(mLockPatternUtils.getLockSettings()).thenReturn(ils);
+        return ils;
+    }
+
+    private IWeakEscrowTokenActivatedListener createWeakEscrowTokenListener() {
+        return new IWeakEscrowTokenActivatedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenActivated(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
+
+    private IWeakEscrowTokenRemovedListener createTestAutoEscrowTokenRemovedListener() {
+        return new IWeakEscrowTokenRemovedListener.Stub() {
+            @Override
+            public void onWeakEscrowTokenRemoved(long handle, int userId) {
+                // Do nothing.
+            }
+        };
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index fab11a1..682a27a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -39,7 +39,10 @@
 import static com.android.internal.widget.LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
+import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG;
+import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -123,6 +126,8 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockscreenCredential;
@@ -135,6 +140,7 @@
 import com.android.server.locksettings.LockSettingsStorage.PersistentData;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
+import com.android.server.locksettings.SyntheticPasswordManager.TokenType;
 import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
@@ -1123,6 +1129,16 @@
         return mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED;
     }
 
+    private void checkManageWeakEscrowTokenMethodUsage() {
+        mContext.enforceCallingOrSelfPermission(
+                Manifest.permission.MANAGE_WEAK_ESCROW_TOKEN,
+                "Requires MANAGE_WEAK_ESCROW_TOKEN permission.");
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            throw new IllegalArgumentException(
+                    "Weak escrow token are only for automotive devices.");
+        }
+    }
+
     @Override
     public boolean hasSecureLockScreen() {
         return mHasSecureLockScreen;
@@ -1911,6 +1927,97 @@
         });
     }
 
+    /** Register the given WeakEscrowTokenRemovedListener. */
+    @Override
+    public boolean registerWeakEscrowTokenRemovedListener(
+            @NonNull IWeakEscrowTokenRemovedListener listener) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mSpManager.registerWeakEscrowTokenRemovedListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /** Unregister the given WeakEscrowTokenRemovedListener. */
+    @Override
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            @NonNull IWeakEscrowTokenRemovedListener listener) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mSpManager.unregisterWeakEscrowTokenRemovedListener(listener);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public long addWeakEscrowToken(byte[] token, int userId,
+            @NonNull IWeakEscrowTokenActivatedListener listener) {
+        checkManageWeakEscrowTokenMethodUsage();
+        Objects.requireNonNull(listener, "Listener can not be null.");
+        EscrowTokenStateChangeCallback internalListener = (handle, userId1) -> {
+            try {
+                listener.onWeakEscrowTokenActivated(handle, userId1);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception while notifying weak escrow token has been activated", e);
+            }
+        };
+        final long restoreToken = Binder.clearCallingIdentity();
+        try {
+            return addEscrowToken(token, TOKEN_TYPE_WEAK, userId, internalListener);
+        } finally {
+            Binder.restoreCallingIdentity(restoreToken);
+        }
+    }
+
+    @Override
+    public boolean removeWeakEscrowToken(long handle, int userId) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return removeEscrowToken(handle, userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean isWeakEscrowTokenActive(long handle, int userId) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return isEscrowTokenActive(handle, userId);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean isWeakEscrowTokenValid(long handle, byte[] token, int userId) {
+        checkManageWeakEscrowTokenMethodUsage();
+        final long restoreToken = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSpManager) {
+                if (!mSpManager.hasEscrowData(userId)) {
+                    Slog.w(TAG, "Escrow token is disabled on the current user");
+                    return false;
+                }
+                AuthenticationResult authResult = mSpManager.unwrapWeakTokenBasedSyntheticPassword(
+                        getGateKeeperService(), handle, token, userId);
+                if (authResult.authToken == null) {
+                    Slog.w(TAG, "Invalid escrow token supplied");
+                    return false;
+                }
+                return true;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(restoreToken);
+        }
+    }
+
     @VisibleForTesting /** Note: this method is overridden in unit tests */
     protected void tieProfileLockToParent(int userId, LockscreenCredential password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
@@ -3029,6 +3136,7 @@
     private long setLockCredentialWithAuthTokenLocked(LockscreenCredential credential,
             AuthenticationToken auth, int userId) {
         if (DEBUG) Slog.d(TAG, "setLockCredentialWithAuthTokenLocked: user=" + userId);
+        final int savedCredentialType = getCredentialTypeInternal(userId);
         long newHandle = mSpManager.createPasswordBasedSyntheticPassword(getGateKeeperService(),
                 credential, auth, userId);
         final Map<Integer, LockscreenCredential> profilePasswords;
@@ -3075,6 +3183,9 @@
 
         setUserPasswordMetrics(credential, userId);
         mManagedProfilePasswordCache.removePassword(userId);
+        if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
+            mSpManager.destroyAllWeakTokenBasedSyntheticPasswords(userId);
+        }
 
         if (profilePasswords != null) {
             for (Map.Entry<Integer, LockscreenCredential> entry : profilePasswords.entrySet()) {
@@ -3242,8 +3353,9 @@
         }
     }
 
-    private long addEscrowToken(byte[] token, int userId, EscrowTokenStateChangeCallback callback) {
-        if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId);
+    private long addEscrowToken(@NonNull byte[] token, @TokenType int type, int userId,
+            @NonNull EscrowTokenStateChangeCallback callback) {
+        if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId + ", type=" + type);
         synchronized (mSpManager) {
             // Migrate to synthetic password based credentials if the user has no password,
             // the token can then be activated immediately.
@@ -3264,7 +3376,8 @@
                     throw new SecurityException("Escrow token is disabled on the current user");
                 }
             }
-            long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId, callback);
+            long handle = mSpManager.createTokenBasedSyntheticPassword(token, type, userId,
+                    callback);
             if (auth != null) {
                 mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId);
             }
@@ -3345,8 +3458,8 @@
     private boolean setLockCredentialWithTokenInternalLocked(LockscreenCredential credential,
             long tokenHandle, byte[] token, int userId) {
         final AuthenticationResult result;
-        result = mSpManager.unwrapTokenBasedSyntheticPassword(
-                getGateKeeperService(), tokenHandle, token, userId);
+        result = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle,
+                token, userId);
         if (result.authToken == null) {
             Slog.w(TAG, "Invalid escrow token supplied");
             return false;
@@ -3369,7 +3482,8 @@
         AuthenticationResult authResult;
         synchronized (mSpManager) {
             if (!mSpManager.hasEscrowData(userId)) {
-                throw new SecurityException("Escrow token is disabled on the current user");
+                Slog.w(TAG, "Escrow token is disabled on the current user");
+                return false;
             }
             authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(),
                     tokenHandle, token, userId);
@@ -3643,7 +3757,8 @@
         @Override
         public long addEscrowToken(byte[] token, int userId,
                 EscrowTokenStateChangeCallback callback) {
-            return LockSettingsService.this.addEscrowToken(token, userId, callback);
+            return LockSettingsService.this.addEscrowToken(token, TOKEN_TYPE_STRONG, userId,
+                    callback);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 601a572..2da4431 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.PasswordMetrics;
@@ -28,6 +29,7 @@
 import android.hardware.weaver.V1_0.WeaverReadResponse;
 import android.hardware.weaver.V1_0.WeaverReadStatus;
 import android.hardware.weaver.V1_0.WeaverStatus;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.security.GateKeeper;
@@ -41,6 +43,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.VerifyCredentialResponse;
@@ -48,6 +51,8 @@
 
 import libcore.util.HexEncoding;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -111,7 +116,8 @@
     private static final byte SYNTHETIC_PASSWORD_VERSION_V2 = 2;
     private static final byte SYNTHETIC_PASSWORD_VERSION_V3 = 3;
     private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0;
-    private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1;
+    private static final byte SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED = 1;
+    private static final byte SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED = 2;
 
     // 256-bit synthetic password
     private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8;
@@ -366,10 +372,47 @@
         }
     }
 
+    static class SyntheticPasswordBlob {
+        byte mVersion;
+        byte mType;
+        byte[] mContent;
+
+        public static SyntheticPasswordBlob create(byte version, byte type, byte[] content) {
+            SyntheticPasswordBlob result = new SyntheticPasswordBlob();
+            result.mVersion = version;
+            result.mType = type;
+            result.mContent = content;
+            return result;
+        }
+
+        public static SyntheticPasswordBlob fromBytes(byte[] data) {
+            SyntheticPasswordBlob result = new SyntheticPasswordBlob();
+            result.mVersion = data[0];
+            result.mType = data[1];
+            result.mContent = Arrays.copyOfRange(data, 2, data.length);
+            return result;
+        }
+
+        public byte[] toByte() {
+            byte[] blob = new byte[mContent.length + 1 + 1];
+            blob[0] = mVersion;
+            blob[1] = mType;
+            System.arraycopy(mContent, 0, blob, 2, mContent.length);
+            return blob;
+        }
+    }
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({TOKEN_TYPE_STRONG, TOKEN_TYPE_WEAK})
+    @interface TokenType {}
+    static final int TOKEN_TYPE_STRONG = 0;
+    static final int TOKEN_TYPE_WEAK = 1;
+
     static class TokenData {
         byte[] secdiscardableOnDisk;
         byte[] weaverSecret;
         byte[] aggregatedSecret;
+        @TokenType int mType;
         EscrowTokenStateChangeCallback mCallback;
     }
 
@@ -381,6 +424,9 @@
 
     private final UserManager mUserManager;
 
+    private final RemoteCallbackList<IWeakEscrowTokenRemovedListener> mListeners =
+            new RemoteCallbackList<>();
+
     public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
             UserManager userManager, PasswordSlotManager passwordSlotManager) {
         mContext = context;
@@ -880,13 +926,34 @@
      * Create a token based Synthetic password for the given user.
      * @return the handle of the token
      */
-    public long createTokenBasedSyntheticPassword(byte[] token, int userId,
+    public long createStrongTokenBasedSyntheticPassword(byte[] token, int userId,
+            @Nullable EscrowTokenStateChangeCallback changeCallback) {
+        return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_STRONG, userId,
+                changeCallback);
+    }
+
+    /**
+     * Create a weak token based Synthetic password for the given user.
+     * @return the handle of the weak token
+     */
+    public long createWeakTokenBasedSyntheticPassword(byte[] token, int userId,
+            @Nullable EscrowTokenStateChangeCallback changeCallback) {
+        return createTokenBasedSyntheticPassword(token, TOKEN_TYPE_WEAK, userId,
+                changeCallback);
+    }
+
+    /**
+     * Create a token based Synthetic password of the given type for the given user.
+     * @return the handle of the token
+     */
+    public long createTokenBasedSyntheticPassword(byte[] token, @TokenType int type, int userId,
             @Nullable EscrowTokenStateChangeCallback changeCallback) {
         long handle = generateHandle();
         if (!tokenMap.containsKey(userId)) {
             tokenMap.put(userId, new ArrayMap<>());
         }
         TokenData tokenData = new TokenData();
+        tokenData.mType = type;
         final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
         if (isWeaverAvailable()) {
             tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
@@ -910,6 +977,7 @@
         return new ArraySet<>(tokenMap.get(userId).keySet());
     }
 
+    /** Remove the given pending token. */
     public boolean removePendingToken(long handle, int userId) {
         if (!tokenMap.containsKey(userId)) {
             return false;
@@ -941,7 +1009,7 @@
             mPasswordSlotManager.markSlotInUse(slot);
         }
         saveSecdiscardable(handle, tokenData.secdiscardableOnDisk, userId);
-        createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken,
+        createSyntheticPasswordBlob(handle, getTokenBasedBlobType(tokenData.mType), authToken,
                 tokenData.aggregatedSecret, 0L, userId);
         tokenMap.get(userId).remove(handle);
         if (tokenData.mCallback != null) {
@@ -953,26 +1021,23 @@
     private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken,
             byte[] applicationId, long sid, int userId) {
         final byte[] secret;
-        if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+        if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
+                || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
             secret = authToken.getEscrowSecret();
         } else {
             secret = authToken.getSyntheticPassword();
         }
         byte[] content = createSPBlob(getKeyName(handle), secret, applicationId, sid);
-        byte[] blob = new byte[content.length + 1 + 1];
         /*
          * We can upgrade from v1 to v2 because that's just a change in the way that
          * the SP is stored. However, we can't upgrade to v3 because that is a change
          * in the way that passwords are derived from the SP.
          */
-        if (authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3) {
-            blob[0] = SYNTHETIC_PASSWORD_VERSION_V3;
-        } else {
-            blob[0] = SYNTHETIC_PASSWORD_VERSION_V2;
-        }
-        blob[1] = type;
-        System.arraycopy(content, 0, blob, 2, content.length);
-        saveState(SP_BLOB_NAME, blob, handle, userId);
+        byte version = authToken.mVersion == SYNTHETIC_PASSWORD_VERSION_V3
+                ? SYNTHETIC_PASSWORD_VERSION_V3 : SYNTHETIC_PASSWORD_VERSION_V2;
+
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob.create(version, type, content);
+        saveState(SP_BLOB_NAME, blob.toByte(), handle, userId);
     }
 
     /**
@@ -1089,6 +1154,36 @@
      */
     public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword(
             IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob
+                .fromBytes(loadState(SP_BLOB_NAME, handle, userId));
+        return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+                blob.mType, token, userId);
+    }
+
+    /**
+     * Decrypt a synthetic password by supplying an strong escrow token and corresponding token
+     * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+     * verification to referesh the SID & Auth token maintained by the system.
+     */
+    public @NonNull AuthenticationResult unwrapStrongTokenBasedSyntheticPassword(
+            IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+        return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+                SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED, token, userId);
+    }
+
+    /**
+     * Decrypt a synthetic password by supplying a weak escrow token and corresponding token
+     * blob handle generated previously. If the decryption is successful, initiate a GateKeeper
+     * verification to referesh the SID & Auth token maintained by the system.
+     */
+    public @NonNull AuthenticationResult unwrapWeakTokenBasedSyntheticPassword(
+            IGateKeeperService gatekeeper, long handle, byte[] token, int userId) {
+        return unwrapTokenBasedSyntheticPasswordInternal(gatekeeper, handle,
+                SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED, token, userId);
+    }
+
+    private  @NonNull AuthenticationResult unwrapTokenBasedSyntheticPasswordInternal(
+            IGateKeeperService gatekeeper, long handle, byte type, byte[] token, int userId) {
         AuthenticationResult result = new AuthenticationResult();
         byte[] secdiscardable = loadSecdiscardable(handle, userId);
         int slotId = loadWeaverSlot(handle, userId);
@@ -1109,8 +1204,7 @@
                     PERSONALISATION_WEAVER_TOKEN, secdiscardable);
         }
         byte[] applicationId = transformUnderSecdiscardable(token, secdiscardable);
-        result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED,
-                applicationId, 0L, userId);
+        result.authToken = unwrapSyntheticPasswordBlob(handle, type, applicationId, 0L, userId);
         if (result.authToken != null) {
             result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId);
             if (result.gkResponse == null) {
@@ -1126,33 +1220,33 @@
 
     private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type,
             byte[] applicationId, long sid, int userId) {
-        byte[] blob = loadState(SP_BLOB_NAME, handle, userId);
-        if (blob == null) {
+        byte[] data = loadState(SP_BLOB_NAME, handle, userId);
+        if (data == null) {
             return null;
         }
-        final byte version = blob[0];
-        if (version != SYNTHETIC_PASSWORD_VERSION_V3
-                && version != SYNTHETIC_PASSWORD_VERSION_V2
-                && version != SYNTHETIC_PASSWORD_VERSION_V1) {
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(data);
+        if (blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V3
+                && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V2
+                && blob.mVersion != SYNTHETIC_PASSWORD_VERSION_V1) {
             throw new IllegalArgumentException("Unknown blob version");
         }
-        if (blob[1] != type) {
+        if (blob.mType != type) {
             throw new IllegalArgumentException("Invalid blob type");
         }
         final byte[] secret;
-        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
-            secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle),
-                    Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+        if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
+            secret = SyntheticPasswordCrypto.decryptBlobV1(getKeyName(handle), blob.mContent,
+                    applicationId);
         } else {
-            secret = decryptSPBlob(getKeyName(handle),
-                Arrays.copyOfRange(blob, 2, blob.length), applicationId);
+            secret = decryptSPBlob(getKeyName(handle), blob.mContent, applicationId);
         }
         if (secret == null) {
             Slog.e(TAG, "Fail to decrypt SP for user " + userId);
             return null;
         }
-        AuthenticationToken result = new AuthenticationToken(version);
-        if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) {
+        AuthenticationToken result = new AuthenticationToken(blob.mVersion);
+        if (type == SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED
+                || type == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
             if (!loadEscrowData(result, userId)) {
                 Slog.e(TAG, "User is not escrowable: " + userId);
                 return null;
@@ -1161,7 +1255,7 @@
         } else {
             result.recreateDirectly(secret);
         }
-        if (version == SYNTHETIC_PASSWORD_VERSION_V1) {
+        if (blob.mVersion == SYNTHETIC_PASSWORD_VERSION_V1) {
             Slog.i(TAG, "Upgrade v1 SP blob for user " + userId + ", type = " + type);
             createSyntheticPasswordBlob(handle, type, result, applicationId, sid, userId);
         }
@@ -1233,9 +1327,28 @@
         return hasState(SP_BLOB_NAME, handle, userId);
     }
 
+    /** Destroy the escrow token with the given handle for the given user. */
     public void destroyTokenBasedSyntheticPassword(long handle, int userId) {
+        SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME, handle,
+                userId));
         destroySyntheticPassword(handle, userId);
         destroyState(SECDISCARDABLE_NAME, handle, userId);
+        if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
+            notifyWeakEscrowTokenRemovedListeners(handle, userId);
+        }
+    }
+
+    /** Destroy all weak escrow tokens for the given user. */
+    public void destroyAllWeakTokenBasedSyntheticPasswords(int userId) {
+        List<Long> handles = mStorage.listSyntheticPasswordHandlesForUser(SECDISCARDABLE_NAME,
+                userId);
+        for (long handle: handles) {
+            SyntheticPasswordBlob blob = SyntheticPasswordBlob.fromBytes(loadState(SP_BLOB_NAME,
+                    handle, userId));
+            if (blob.mType == SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED) {
+                destroyTokenBasedSyntheticPassword(handle, userId);
+            }
+        }
     }
 
     public void destroyPasswordBasedSyntheticPassword(long handle, int userId) {
@@ -1285,6 +1398,16 @@
         return loadState(SECDISCARDABLE_NAME, handle, userId);
     }
 
+    private byte getTokenBasedBlobType(@TokenType int type) {
+        switch (type) {
+            case TOKEN_TYPE_WEAK:
+                return SYNTHETIC_PASSWORD_WEAK_TOKEN_BASED;
+            case TOKEN_TYPE_STRONG:
+            default:
+                return SYNTHETIC_PASSWORD_STRONG_TOKEN_BASED;
+        }
+    }
+
     /**
      * Retrieves the saved password metrics associated with a SP handle. Only meaningful to be
      * called on the handle of a password-based synthetic password. A valid AuthenticationToken for
@@ -1439,4 +1562,33 @@
         }
         return success;
     }
+
+    /** Register the given IWeakEscrowTokenRemovedListener. */
+    public boolean registerWeakEscrowTokenRemovedListener(
+            IWeakEscrowTokenRemovedListener listener) {
+        return mListeners.register(listener);
+    }
+
+    /** Unregister the given IWeakEscrowTokenRemovedListener. */
+    public boolean unregisterWeakEscrowTokenRemovedListener(
+            IWeakEscrowTokenRemovedListener listener) {
+        return mListeners.unregister(listener);
+    }
+
+    private void notifyWeakEscrowTokenRemovedListeners(long handle, int userId) {
+        int i = mListeners.beginBroadcast();
+        try {
+            while (i > 0) {
+                i--;
+                try {
+                    mListeners.getBroadcastItem(i).onWeakEscrowTokenRemoved(handle, userId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception while notifying WeakEscrowTokenRemovedListener.",
+                            e);
+                }
+            }
+        } finally {
+            mListeners.finishBroadcast();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
new file mode 100644
index 0000000..51ddcef
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/WeakEscrowTokenTests.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 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.locksettings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+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.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.IWeakEscrowTokenActivatedListener;
+import com.android.internal.widget.IWeakEscrowTokenRemovedListener;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.VerifyCredentialResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** atest FrameworksServicesTests:WeakEscrowTokenTests */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WeakEscrowTokenTests extends BaseLockSettingsServiceTests{
+
+    @Test
+    public void testWeakTokenActivatedImmediatelyIfNoUserPassword()
+            throws RemoteException {
+        mockAutoHardware();
+        final byte[] token = "some-high-entropy-secure-token".getBytes();
+        IWeakEscrowTokenActivatedListener mockListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+        verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokenActivatedLaterWithUserPassword()
+            throws RemoteException {
+        mockAutoHardware();
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        IWeakEscrowTokenActivatedListener mockListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        LockscreenCredential password = newPassword("password");
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockListener);
+        // Token not activated immediately since user password exists
+        assertFalse(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        // Activate token (password gets migrated to SP at the same time)
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+        // Verify token is activated and valid
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+        verify(mockListener).onWeakEscrowTokenActivated(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokensRemovedIfCredentialChanged() throws Exception {
+        mockAutoHardware();
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        LockscreenCredential password = newPassword("password");
+        LockscreenCredential pattern = newPattern("123654");
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+        mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+
+        // Activate token
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+
+        // Verify token removed
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mLocalService.setLockCredentialWithToken(
+                pattern, handle, token, PRIMARY_USER_ID));
+        assertFalse(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+        verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokenRemovedListenerRegistered() throws Exception {
+        mockAutoHardware();
+        IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+
+        mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+        mService.removeWeakEscrowToken(handle, PRIMARY_USER_ID);
+
+        verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testWeakTokenRemovedListenerUnregistered() throws Exception {
+        mockAutoHardware();
+        IWeakEscrowTokenRemovedListener mockRemoveListener = mockAliveRemoveListener();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        byte[] token0 = "some-high-entropy-secure-token-0".getBytes();
+        byte[] token1 = "some-high-entropy-secure-token-1".getBytes();
+        long handle0 = mService.addWeakEscrowToken(token0, PRIMARY_USER_ID, mockActivateListener);
+        long handle1 = mService.addWeakEscrowToken(token1, PRIMARY_USER_ID, mockActivateListener);
+
+        mService.registerWeakEscrowTokenRemovedListener(mockRemoveListener);
+        mService.removeWeakEscrowToken(handle0, PRIMARY_USER_ID);
+        verify(mockRemoveListener).onWeakEscrowTokenRemoved(handle0, PRIMARY_USER_ID);
+
+        mService.unregisterWeakEscrowTokenRemovedListener(mockRemoveListener);
+        mService.removeWeakEscrowToken(handle1, PRIMARY_USER_ID);
+        verify(mockRemoveListener, never()).onWeakEscrowTokenRemoved(handle1, PRIMARY_USER_ID);
+    }
+
+    @Test
+    public void testUnlockUserWithToken_weakEscrowToken() throws Exception {
+        mockAutoHardware();
+        IWeakEscrowTokenActivatedListener mockActivateListener =
+                mock(IWeakEscrowTokenActivatedListener.Stub.class);
+        LockscreenCredential password = newPassword("password");
+        byte[] token = "some-high-entropy-secure-token".getBytes();
+        mService.setLockCredential(password, nonePassword(), PRIMARY_USER_ID);
+        // Disregard any reportPasswordChanged() invocations as part of credential setup.
+        flushHandlerTasks();
+        reset(mDevicePolicyManager);
+
+        long handle = mService.addWeakEscrowToken(token, PRIMARY_USER_ID, mockActivateListener);
+        assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
+                password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
+        assertTrue(mService.isWeakEscrowTokenActive(handle, PRIMARY_USER_ID));
+        assertTrue(mService.isWeakEscrowTokenValid(handle, token, PRIMARY_USER_ID));
+
+        mService.onCleanupUser(PRIMARY_USER_ID);
+        assertNull(mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
+
+        assertTrue(mLocalService.unlockUserWithToken(handle, token, PRIMARY_USER_ID));
+        assertEquals(PasswordMetrics.computeForCredential(password),
+                mLocalService.getUserPasswordMetrics(PRIMARY_USER_ID));
+    }
+
+    private void mockAutoHardware() {
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true);
+    }
+
+    private IWeakEscrowTokenRemovedListener mockAliveRemoveListener() {
+        IWeakEscrowTokenRemovedListener mockListener =
+                mock(IWeakEscrowTokenRemovedListener.Stub.class);
+        IBinder mockIBinder = mock(IBinder.class);
+        when(mockIBinder.isBinderAlive()).thenReturn(true);
+        when(mockListener.asBinder()).thenReturn(mockIBinder);
+        return mockListener;
+    }
+}