Merge "Persistent process should have all capabilities."
diff --git a/apex/sdkextensions/Android.bp b/apex/sdkextensions/Android.bp
index 4c5c2b2..25765af 100644
--- a/apex/sdkextensions/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -28,7 +28,6 @@
     name: "com.android.sdkext-defaults",
     java_libs: [ "framework-sdkextensions" ],
     prebuilts: [
-        "com.android.sdkext.ldconfig",
         "derive_sdk.rc",
     ],
     key: "com.android.sdkext.key",
@@ -51,13 +50,6 @@
     certificate: "com.android.sdkext",
 }
 
-prebuilt_etc {
-    name: "com.android.sdkext.ldconfig",
-    src: "ld.config.txt",
-    filename: "ld.config.txt",
-    installable: false,
-}
-
 python_binary_host {
     name: "gen_sdkinfo",
     srcs: [
diff --git a/apex/sdkextensions/ld.config.txt b/apex/sdkextensions/ld.config.txt
deleted file mode 100644
index dcc69b8..0000000
--- a/apex/sdkextensions/ld.config.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Bionic loader config file for the sdkextensions apex.
-
-dir.sdkextensions = /apex/com.android.sdkext/bin/
-
-[sdkextensions]
-additional.namespaces = platform
-
-namespace.default.isolated = true
-namespace.default.links = platform
-namespace.default.link.platform.allow_all_shared_libs = true
-
-###############################################################################
-# "platform" namespace: used for NDK libraries
-###############################################################################
-namespace.platform.isolated = true
-namespace.platform.search.paths = /system/${LIB}
-namespace.platform.asan.search.paths = /data/asan/system/${LIB}
-
-# /system/lib/libc.so, etc are symlinks to /apex/com.android.lib/lib/bionic/libc.so, etc.
-# Add /apex/... path to the permitted paths because linker uses realpath(3)
-# to check the accessibility of the lib. We could add this to search.paths
-# instead but that makes the resolution of bionic libs be dependent on
-# the order of /system/lib and /apex/... in search.paths. If /apex/...
-# is after /system/lib, then /apex/... is never tried because libc.so
-# is always found in /system/lib but fails to pass the accessibility test
-# because of its realpath.  It's better to not depend on the ordering if
-# possible.
-namespace.platform.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
-namespace.platform.asan.permitted.paths = /apex/com.android.runtime/${LIB}/bionic
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index 526d17f..e637187 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -159,6 +159,9 @@
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to addConfig in statsmanager");
+                throw new StatsUnavailableException(e.getMessage(), e);
             }
         }
     }
@@ -195,6 +198,9 @@
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to removeConfig in statsmanager");
+                throw new StatsUnavailableException(e.getMessage(), e);
             }
         }
     }
@@ -391,6 +397,9 @@
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to getReports in statsmanager");
+                throw new StatsUnavailableException(e.getMessage(), e);
             }
         }
     }
@@ -428,6 +437,9 @@
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to getStatsMetadata in statsmanager");
+                throw new StatsUnavailableException(e.getMessage(), e);
             }
         }
     }
@@ -469,6 +481,9 @@
                                     + "registered experiment IDs");
                 }
                 throw new StatsUnavailableException("could not connect", e);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to getRegisteredExperimentIds in statsmanager");
+                throw new StatsUnavailableException(e.getMessage(), e);
             }
         }
     }
diff --git a/api/current.txt b/api/current.txt
index 476d0ae..0a7bac5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2992,7 +2992,7 @@
     method public int getNonInteractiveUiTimeoutMillis();
     method public android.content.pm.ResolveInfo getResolveInfo();
     method public String getSettingsActivityName();
-    method @Nullable public android.graphics.drawable.Drawable loadAnimatedImage(@NonNull android.content.pm.PackageManager);
+    method @Nullable public android.graphics.drawable.Drawable loadAnimatedImage(@NonNull android.content.Context);
     method public String loadDescription(android.content.pm.PackageManager);
     method @Nullable public String loadHtmlDescription(@NonNull android.content.pm.PackageManager);
     method public CharSequence loadSummary(android.content.pm.PackageManager);
@@ -26899,21 +26899,27 @@
     ctor public MediaRoute2ProviderService();
     method @NonNull public final java.util.List<android.media.RoutingSessionInfo> getAllSessionInfo();
     method @Nullable public final android.media.RoutingSessionInfo getSessionInfo(@NonNull String);
+    method public final void notifyRequestFailed(long, int);
     method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>);
     method public final void notifySessionCreated(@NonNull android.media.RoutingSessionInfo, long);
     method public final void notifySessionCreationFailed(long);
     method public final void notifySessionReleased(@NonNull String);
     method public final void notifySessionUpdated(@NonNull android.media.RoutingSessionInfo);
     method @CallSuper @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public abstract void onCreateSession(@NonNull String, @NonNull String, long, @Nullable android.os.Bundle);
-    method public abstract void onDeselectRoute(@NonNull String, @NonNull String);
+    method public abstract void onCreateSession(long, @NonNull String, @NonNull String, @Nullable android.os.Bundle);
+    method public abstract void onDeselectRoute(long, @NonNull String, @NonNull String);
     method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
-    method public abstract void onReleaseSession(@NonNull String);
-    method public abstract void onSelectRoute(@NonNull String, @NonNull String);
-    method public abstract void onSetRouteVolume(@NonNull String, int);
-    method public abstract void onSetSessionVolume(@NonNull String, int);
-    method public abstract void onTransferToRoute(@NonNull String, @NonNull String);
-    field public static final long REQUEST_ID_UNKNOWN = 0L; // 0x0L
+    method public abstract void onReleaseSession(long, @NonNull String);
+    method public abstract void onSelectRoute(long, @NonNull String, @NonNull String);
+    method public abstract void onSetRouteVolume(long, @NonNull String, int);
+    method public abstract void onSetSessionVolume(long, @NonNull String, int);
+    method public abstract void onTransferToRoute(long, @NonNull String, @NonNull String);
+    field public static final int REASON_INVALID_COMMAND = 4; // 0x4
+    field public static final int REASON_NETWORK_ERROR = 2; // 0x2
+    field public static final int REASON_REJECTED = 1; // 0x1
+    field public static final int REASON_ROUTE_NOT_AVAILABLE = 3; // 0x3
+    field public static final int REASON_UNKNOWN_ERROR = 0; // 0x0
+    field public static final long REQUEST_ID_NONE = 0L; // 0x0L
     field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
   }
 
diff --git a/api/system-current.txt b/api/system-current.txt
index 9663c0c..f4a1d841 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8757,7 +8757,9 @@
 
   public class UserManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void clearSeedAccountData();
-    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @Nullable String[]) throws android.os.UserManager.UserOperationException;
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.UserHandle createProfile(@NonNull String, @NonNull String, @NonNull java.util.Set<java.lang.String>) throws android.os.UserManager.UserOperationException;
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getAllProfiles();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getEnabledProfiles();
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getSeedAccountName();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.PersistableBundle getSeedAccountOptions();
@@ -8765,7 +8767,6 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public long[] getSerialNumbersOfUsers(boolean);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserHandle> getUserHandles(boolean);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional=true) public java.util.List<android.os.UserHandle> getUserProfiles(boolean);
     method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public int getUserRestrictionSource(String, android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
     method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 4899b4a..dcfdfe3 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -75,11 +75,11 @@
                         (long long)id);
 }
 
-static const char* findTrainInfoFileNameLocked(const string& trainName) {
+static string findTrainInfoFileNameLocked(const string& trainName) {
     unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir);
     if (dir == NULL) {
         VLOG("Path %s does not exist", TRAIN_INFO_DIR);
-        return nullptr;
+        return "";
     }
     dirent* de;
     while ((de = readdir(dir.get()))) {
@@ -90,12 +90,12 @@
         if (fileNameLength >= trainName.length()) {
             if (0 == strncmp(fileName + fileNameLength - trainName.length(), trainName.c_str(),
                              trainName.length())) {
-                return fileName;
+              return string(fileName);
             }
         }
     }
 
-    return nullptr;
+    return "";
 }
 
 // Returns array of int64_t which contains timestamp in seconds, uid,
@@ -267,13 +267,13 @@
 
 bool StorageManager::readTrainInfoLocked(const std::string& trainName, InstallTrainInfo& trainInfo) {
     trimToFit(TRAIN_INFO_DIR, /*parseTimestampOnly=*/ true);
-    const char* fileName = findTrainInfoFileNameLocked(trainName);
-    if (fileName == nullptr) {
+    string fileName = findTrainInfoFileNameLocked(trainName);
+    if (fileName.empty()) {
         return false;
     }
-    int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName).c_str(), O_RDONLY | O_CLOEXEC);
+    int fd = open(StringPrintf("%s/%s", TRAIN_INFO_DIR, fileName.c_str()).c_str(), O_RDONLY | O_CLOEXEC);
     if (fd == -1) {
-        VLOG("Failed to open %s", fileName);
+        VLOG("Failed to open %s", fileName.c_str());
         return false;
     }
 
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 9707405..fe270a4 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -46,6 +46,10 @@
      * @param args The command-line arguments
      */
     public static void main(String[] args) {
+        // Initialize the telephony module.
+        // TODO: Do it in zygote and RuntimeInit. b/148897549
+        ActivityThread.initializeMainlineModules();
+
       (new Telecom()).run(args);
     }
 
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index c373284..3b0667d 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -803,11 +803,12 @@
      * @return The animated image drawable.
      */
     @Nullable
-    public Drawable loadAnimatedImage(@NonNull PackageManager packageManager)  {
+    public Drawable loadAnimatedImage(@NonNull Context context)  {
         if (mAnimatedImageRes == /* invalid */ 0) {
             return null;
         }
 
+        final PackageManager packageManager = context.getPackageManager();
         final String packageName = mComponentName.getPackageName();
         final ApplicationInfo applicationInfo = mResolveInfo.serviceInfo.applicationInfo;
 
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index d537ce1..6209679 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -208,11 +208,12 @@
      * @return The animated image drawable.
      */
     @Nullable
-    public Drawable loadAnimatedImage(@NonNull PackageManager packageManager) {
+    public Drawable loadAnimatedImage(@NonNull Context context) {
         if (mAnimatedImageRes == /* invalid */ 0) {
             return null;
         }
 
+        final PackageManager packageManager = context.getPackageManager();
         final String packageName = mComponentName.getPackageName();
         final ApplicationInfo applicationInfo = mActivityInfo.applicationInfo;
 
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 55be082..b219394 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9932,20 +9932,27 @@
     }
 
     /**
-     * Called by device owner to control the security logging feature.
+     * Called by device owner or a profile owner of an organization-owned managed profile to
+     * control the security logging feature.
      *
      * <p> Security logs contain various information intended for security auditing purposes.
-     * See {@link SecurityEvent} for details.
+     * When security logging is enabled by a profile owner of
+     * an organization-owned managed profile, certain security logs are not visible (for example
+     * personal app launch events) or they will be redacted (for example, details of the physical
+     * volume mount events). Please see {@link SecurityEvent} for details.
      *
      * <p><strong>Note:</strong> The device owner won't be able to retrieve security logs if there
      * are unaffiliated secondary users or profiles on the device, regardless of whether the
      * feature is enabled. Logs will be discarded if the internal buffer fills up while waiting for
      * all users to become affiliated. Therefore it's recommended that affiliation ids are set for
-     * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
+     * new users as soon as possible after provisioning via {@link #setAffiliationIds}. Profile
+     * owner of organization-owned managed profile is not subject to this restriction since all
+     * privacy-sensitive events happening outside the managed profile would have been redacted
+     * already.
      *
-     * @param admin Which device owner this request is associated with.
+     * @param admin Which device admin this request is associated with.
      * @param enabled whether security logging should be enabled or not.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not allowed to control security logging.
      * @see #setAffiliationIds
      * @see #retrieveSecurityLogs
      */
@@ -9959,14 +9966,14 @@
     }
 
     /**
-     * Return whether security logging is enabled or not by the device owner.
+     * Return whether security logging is enabled or not by the admin.
      *
-     * <p>Can only be called by the device owner, otherwise a {@link SecurityException} will be
-     * thrown.
+     * <p>Can only be called by the device owner or a profile owner of an organization-owned
+     * managed profile, otherwise a {@link SecurityException} will be thrown.
      *
-     * @param admin Which device owner this request is associated with.
+     * @param admin Which device admin this request is associated with.
      * @return {@code true} if security logging is enabled by device owner, {@code false} otherwise.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if {@code admin} is not allowed to control security logging.
      */
     public boolean isSecurityLoggingEnabled(@Nullable ComponentName admin) {
         throwIfParentInstance("isSecurityLoggingEnabled");
@@ -9978,20 +9985,21 @@
     }
 
     /**
-     * Called by device owner to retrieve all new security logging entries since the last call to
-     * this API after device boots.
+     * Called by device owner or profile owner of an organization-owned managed profile to retrieve
+     * all new security logging entries since the last call to this API after device boots.
      *
      * <p> Access to the logs is rate limited and it will only return new logs after the device
      * owner has been notified via {@link DeviceAdminReceiver#onSecurityLogsAvailable}.
      *
-     * <p>If there is any other user or profile on the device, it must be affiliated with the
-     * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}.
+     * <p> When called by a device owner, if there is any other user or profile on the device,
+     * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
+     * See {@link #isAffiliatedUser}.
      *
-     * @param admin Which device owner this request is associated with.
+     * @param admin Which device admin this request is associated with.
      * @return the new batch of security logs which is a list of {@link SecurityEvent},
      * or {@code null} if rate limitation is exceeded or if logging is currently disabled.
-     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
-     * profile or secondary user that is not affiliated with the device.
+     * @throws SecurityException if {@code admin} is not allowed to access security logging,
+     * or there is at least one profile or secondary user that is not affiliated with the device.
      * @see #isAffiliatedUser
      * @see DeviceAdminReceiver#onSecurityLogsAvailable
      */
@@ -10124,21 +10132,23 @@
     }
 
     /**
-     * Called by device owners to retrieve device logs from before the device's last reboot.
+     * Called by device owner or profile owner of an organization-owned managed profile to retrieve
+     * device logs from before the device's last reboot.
      * <p>
      * <strong> This API is not supported on all devices. Calling this API on unsupported devices
      * will result in {@code null} being returned. The device logs are retrieved from a RAM region
      * which is not guaranteed to be corruption-free during power cycles, as a result be cautious
      * about data corruption when parsing. </strong>
      *
-     * <p>If there is any other user or profile on the device, it must be affiliated with the
-     * device. Otherwise a {@link SecurityException} will be thrown. See {@link #isAffiliatedUser}.
+     * <p> When called by a device owner, if there is any other user or profile on the device,
+     * it must be affiliated with the device. Otherwise a {@link SecurityException} will be thrown.
+     * See {@link #isAffiliatedUser}.
      *
-     * @param admin Which device owner this request is associated with.
+     * @param admin Which device admin this request is associated with.
      * @return Device logs from before the latest reboot of the system, or {@code null} if this API
      *         is not supported on the device.
-     * @throws SecurityException if {@code admin} is not a device owner, or there is at least one
-     * profile or secondary user that is not affiliated with the device.
+     * @throws SecurityException if {@code admin} is not allowed to access security logging, or
+     * there is at least one profile or secondary user that is not affiliated with the device.
      * @see #isAffiliatedUser
      * @see #retrieveSecurityLogs
      */
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 91cf120..fb7f573 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -23,11 +23,13 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.util.EventLog.Event;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Objects;
 
@@ -104,7 +106,8 @@
     /**
      * Indicates that a shell command was issued over ADB via {@code adb shell <command>}
      * The log entry contains a {@code String} payload containing the shell command, accessible
-     * via {@link SecurityEvent#getData()}.
+     * via {@link SecurityEvent#getData()}. If security logging is enabled on organization-owned
+     * managed profile devices, the shell command will be redacted to an empty string.
      */
     public static final int TAG_ADB_SHELL_CMD = SecurityLogTags.SECURITY_ADB_SHELL_COMMAND;
 
@@ -133,6 +136,8 @@
      * <li> [3] app pid ({@code Integer})
      * <li> [4] seinfo tag ({@code String})
      * <li> [5] SHA-256 hash of the base APK in hexadecimal ({@code String})
+     * If security logging is enabled on organization-owned managed profile devices, only events
+     * happening inside the managed profile will be visible.
      */
     public static final int TAG_APP_PROCESS_START = SecurityLogTags.SECURITY_APP_PROCESS_START;
 
@@ -205,7 +210,8 @@
      * following information about the event, encapsulated in an {@link Object} array and
      * accessible via {@link SecurityEvent#getData()}:
      * <li> [0] mount point ({@code String})
-     * <li> [1] volume label ({@code String}).
+     * <li> [1] volume label ({@code String}). Redacted to empty string on organization-owned
+     *     managed profile devices.
      */
     public static final int TAG_MEDIA_MOUNT = SecurityLogTags.SECURITY_MEDIA_MOUNTED;
 
@@ -214,7 +220,8 @@
      * following information about the event, encapsulated in an {@link Object} array and
      * accessible via {@link SecurityEvent#getData()}:
      * <li> [0] mount point ({@code String})
-     * <li> [1] volume label ({@code String}).
+     * <li> [1] volume label ({@code String}). Redacted to empty string on organization-owned
+     *     managed profile devices.
      */
     public static final int TAG_MEDIA_UNMOUNT = SecurityLogTags.SECURITY_MEDIA_UNMOUNTED;
 
@@ -340,6 +347,9 @@
      * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
      * <li> [1] alias of the key ({@code String})
      * <li> [2] requesting process uid ({@code Integer}).
+     *
+     * If security logging is enabled on organization-owned managed profile devices, only events
+     * happening inside the managed profile will be visible.
      */
     public static final int TAG_KEY_GENERATED =
             SecurityLogTags.SECURITY_KEY_GENERATED;
@@ -351,6 +361,9 @@
      * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
      * <li> [1] alias of the key ({@code String})
      * <li> [2] requesting process uid ({@code Integer}).
+     *
+     * If security logging is enabled on organization-owned managed profile devices, only events
+     * happening inside the managed profile will be visible.
      */
     public static final int TAG_KEY_IMPORT = SecurityLogTags.SECURITY_KEY_IMPORTED;
 
@@ -361,6 +374,9 @@
      * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
      * <li> [1] alias of the key ({@code String})
      * <li> [2] requesting process uid ({@code Integer}).
+     *
+     * If security logging is enabled on organization-owned managed profile devices, only events
+     * happening inside the managed profile will be visible.
      */
     public static final int TAG_KEY_DESTRUCTION = SecurityLogTags.SECURITY_KEY_DESTROYED;
 
@@ -370,6 +386,11 @@
      * {@link Object} array and accessible via {@link SecurityEvent#getData()}:
      * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
      * <li> [1] subject of the certificate ({@code String}).
+     * <li> [2] which user the certificate is installed for ({@code Integer}), only available from
+     *   version {@link android.os.Build.VERSION_CODES#R}.
+     *
+     * If security logging is enabled on organization-owned managed profile devices, only events
+     * happening inside the managed profile will be visible.
      */
     public static final int TAG_CERT_AUTHORITY_INSTALLED =
             SecurityLogTags.SECURITY_CERT_AUTHORITY_INSTALLED;
@@ -380,6 +401,11 @@
      * {@link Object} array and accessible via {@link SecurityEvent#getData()}:
      * <li> [0] result ({@code Integer}, 0 if operation failed, 1 if succeeded)
      * <li> [1] subject of the certificate ({@code String}).
+     * <li> [2] which user the certificate is removed from ({@code Integer}), only available from
+     *   version {@link android.os.Build.VERSION_CODES#R}.
+     *
+     * If security logging is enabled on organization-owned managed profile devices, only events
+     * happening inside the managed profile will be visible.
      */
     public static final int TAG_CERT_AUTHORITY_REMOVED =
             SecurityLogTags.SECURITY_CERT_AUTHORITY_REMOVED;
@@ -422,6 +448,9 @@
      * {@link SecurityEvent#getData()}:
      * <li> [0] alias of the key ({@code String})
      * <li> [1] owner application uid ({@code Integer}).
+     *
+     * If security logging is enabled on organization-owned managed profile devices, only events
+     * happening inside the managed profile will be visible.
      */
     public static final int TAG_KEY_INTEGRITY_VIOLATION =
             SecurityLogTags.SECURITY_KEY_INTEGRITY_VIOLATION;
@@ -535,6 +564,16 @@
             return mEvent.getData();
         }
 
+        /** @hide */
+        public int getIntegerData(int index) {
+            return (Integer) ((Object[]) mEvent.getData())[index];
+        }
+
+        /** @hide */
+        public String getStringData(int index) {
+            return (String) ((Object[]) mEvent.getData())[index];
+        }
+
         /**
          * @hide
          */
@@ -554,7 +593,7 @@
          * Returns severity level for the event.
          */
         public @SecurityLogLevel int getLogLevel() {
-            switch (mEvent.getTag()) {
+            switch (getTag()) {
                 case TAG_ADB_SHELL_INTERACTIVE:
                 case TAG_ADB_SHELL_CMD:
                 case TAG_SYNC_RECV_FILE:
@@ -608,6 +647,75 @@
             return array.length >= 1 && array[0] instanceof Integer && (Integer) array[0] != 0;
         }
 
+        /**
+         * Returns a copy of the security event suitable to be consumed by the provided user.
+         * This method will either return the original event itself if the event does not contain
+         * any sensitive data; or a copy of itself but with sensitive information redacted; or
+         * {@code null} if the entire event should not be accessed by the given user.
+         *
+         * @param accessingUser which user this security event is to be accessed, must be a
+         *     concrete user id.
+         * @hide
+         */
+        public SecurityEvent redact(int accessingUser) {
+            // Which user the event is associated with, for the purpose of log redaction.
+            final int userId;
+            switch (getTag()) {
+                case SecurityLog.TAG_ADB_SHELL_CMD:
+                    return new SecurityEvent(getId(), mEvent.withNewData("").getBytes());
+                case SecurityLog.TAG_MEDIA_MOUNT:
+                case SecurityLog.TAG_MEDIA_UNMOUNT:
+                    // Partial redaction
+                    String mountPoint;
+                    try {
+                        mountPoint = getStringData(0);
+                    } catch (Exception e) {
+                        return null;
+                    }
+                    return new SecurityEvent(getId(),
+                            mEvent.withNewData(new Object[] {mountPoint, ""}).getBytes());
+                case SecurityLog.TAG_APP_PROCESS_START:
+                    try {
+                        userId = UserHandle.getUserId(getIntegerData(2));
+                    } catch (Exception e) {
+                        return null;
+                    }
+                    break;
+                case SecurityLog.TAG_CERT_AUTHORITY_INSTALLED:
+                case SecurityLog.TAG_CERT_AUTHORITY_REMOVED:
+                    try {
+                        userId = getIntegerData(2);
+                    } catch (Exception e) {
+                        return null;
+                    }
+                    break;
+                case SecurityLog.TAG_KEY_GENERATED:
+                case SecurityLog.TAG_KEY_IMPORT:
+                case SecurityLog.TAG_KEY_DESTRUCTION:
+                    try {
+                        userId = UserHandle.getUserId(getIntegerData(2));
+                    } catch (Exception e) {
+                        return null;
+                    }
+                    break;
+                case SecurityLog.TAG_KEY_INTEGRITY_VIOLATION:
+                    try {
+                        userId = UserHandle.getUserId(getIntegerData(1));
+                    } catch (Exception e) {
+                        return null;
+                    }
+                    break;
+                default:
+                    userId = UserHandle.USER_NULL;
+            }
+            // If the event is not user-specific, or matches the accessing user, return it
+            // unmodified, else redact by returning null
+            if (userId == UserHandle.USER_NULL || accessingUser == userId) {
+                return this;
+            } else {
+                return null;
+            }
+        }
 
         @Override
         public int describeContents() {
@@ -657,6 +765,30 @@
             return other != null && mEvent.equals(other.mEvent);
         }
     }
+
+    /**
+     * Redacts events in-place according to which user will consume the events.
+     *
+     * @param accessingUser which user will consume the redacted events, or UserHandle.USER_ALL if
+     *     redaction should be skipped.
+     * @hide
+     */
+    public static void redactEvents(ArrayList<SecurityEvent> logList, int accessingUser) {
+        if (accessingUser == UserHandle.USER_ALL) return;
+        int end = 0;
+        for (int i = 0; i < logList.size(); i++) {
+            SecurityEvent event = logList.get(i);
+            event = event.redact(accessingUser);
+            if (event != null) {
+                logList.set(end, event);
+                end++;
+            }
+        }
+        for (int i = logList.size() - 1; i >= end; i--) {
+            logList.remove(i);
+        }
+    }
+
     /**
      * Retrieve all security logs and return immediately.
      * @hide
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index 4e67fe2..100fd4c 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -33,8 +33,8 @@
 210026 security_key_destroyed                   (success|1),(key_id|3),(uid|1)
 210027 security_user_restriction_added          (package|3),(admin_user|1),(restriction|3)
 210028 security_user_restriction_removed        (package|3),(admin_user|1),(restriction|3)
-210029 security_cert_authority_installed        (success|1),(subject|3)
-210030 security_cert_authority_removed          (success|1),(subject|3)
+210029 security_cert_authority_installed        (success|1),(subject|3),(target_user|1)
+210030 security_cert_authority_removed          (success|1),(subject|3),(target_user|1)
 210031 security_crypto_self_test_completed      (success|1)
 210032 security_key_integrity_violation         (key_id|3),(uid|1)
 210033 security_cert_validation_failure         (reason|3)
diff --git a/core/java/android/app/trust/IStrongAuthTracker.aidl b/core/java/android/app/trust/IStrongAuthTracker.aidl
index 36c71bf..6d54396 100644
--- a/core/java/android/app/trust/IStrongAuthTracker.aidl
+++ b/core/java/android/app/trust/IStrongAuthTracker.aidl
@@ -23,4 +23,5 @@
  */
 oneway interface IStrongAuthTracker {
     void onStrongAuthRequiredChanged(int strongAuthRequired, int userId);
+    void onIsNonStrongBiometricAllowedChanged(boolean allowed, int userId);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index b5f4f80..8a89840 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -95,9 +95,9 @@
 
     void registerShortcutChangeCallback(String callingPackage, long changedSince,
             String packageName, in List shortcutIds, in List<LocusId> locusIds,
-            in ComponentName componentName, int flags, in IShortcutChangeCallback callback,
-            int callbackId);
-    void unregisterShortcutChangeCallback(String callingPackage, int callbackId);
+            in ComponentName componentName, int flags, in IShortcutChangeCallback callback);
+    void unregisterShortcutChangeCallback(String callingPackage,
+            in IShortcutChangeCallback callback);
 
     void cacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
             in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 5aa0208..86242fd 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import static android.Manifest.permission;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -161,7 +162,7 @@
     private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>();
     private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>();
 
-    private final Map<Integer, Pair<Executor, ShortcutChangeCallback>>
+    private final Map<ShortcutChangeCallback, Pair<Executor, IShortcutChangeCallback>>
             mShortcutChangeCallbacks = new HashMap<>();
 
     /**
@@ -549,8 +550,8 @@
             android.content.pm.IShortcutChangeCallback.Stub {
         private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences;
 
-        ShortcutChangeCallbackProxy(Pair<Executor, ShortcutChangeCallback> remoteReferences) {
-            mRemoteReferences = new WeakReference<>(remoteReferences);
+        ShortcutChangeCallbackProxy(Executor executor, ShortcutChangeCallback callback) {
+            mRemoteReferences = new WeakReference<>(new Pair<>(executor, callback));
         }
 
         @Override
@@ -1753,14 +1754,12 @@
         Objects.requireNonNull(executor, "Executor cannot be null");
 
         synchronized (mShortcutChangeCallbacks) {
-            final int callbackId = callback.hashCode();
-            final Pair<Executor, ShortcutChangeCallback> state = new Pair<>(executor, callback);
-            mShortcutChangeCallbacks.put(callbackId, state);
+            IShortcutChangeCallback proxy = new ShortcutChangeCallbackProxy(executor, callback);
+            mShortcutChangeCallbacks.put(callback, new Pair<>(executor, proxy));
             try {
                 mService.registerShortcutChangeCallback(mContext.getPackageName(),
                         query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
-                        query.mActivity, query.mQueryFlags, new ShortcutChangeCallbackProxy(state),
-                        callbackId);
+                        query.mActivity, query.mQueryFlags, proxy);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
@@ -1779,12 +1778,10 @@
         Objects.requireNonNull(callback, "Callback cannot be null");
 
         synchronized (mShortcutChangeCallbacks) {
-            final int callbackId = callback.hashCode();
-            if (mShortcutChangeCallbacks.containsKey(callbackId)) {
-                mShortcutChangeCallbacks.remove(callbackId);
+            if (mShortcutChangeCallbacks.containsKey(callback)) {
+                IShortcutChangeCallback proxy = mShortcutChangeCallbacks.remove(callback).second;
                 try {
-                    mService.unregisterShortcutChangeCallback(mContext.getPackageName(),
-                            callbackId);
+                    mService.unregisterShortcutChangeCallback(mContext.getPackageName(), proxy);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 3a93421..a50ce92 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -65,6 +65,9 @@
 
     public abstract void addListener(@NonNull ShortcutChangeListener listener);
 
+    public abstract void addShortcutChangeCallback(
+            @NonNull LauncherApps.ShortcutChangeCallback callback);
+
     public abstract int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
             @NonNull String packageName, @NonNull String shortcutId, int userId);
 
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 3a0660d..9b9889e 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -97,8 +97,10 @@
         }
 
         @Override // binder call
-        public void onAuthenticationSucceeded(long deviceId, Face face, int userId) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, face).sendToTarget();
+        public void onAuthenticationSucceeded(long deviceId, Face face, int userId,
+                boolean isStrongBiometric) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
+                    face).sendToTarget();
         }
 
         @Override // binder call
@@ -814,6 +816,7 @@
         private Face mFace;
         private CryptoObject mCryptoObject;
         private int mUserId;
+        private boolean mIsStrongBiometric;
 
         /**
          * Authentication result
@@ -822,10 +825,12 @@
          * @param face   the recognized face data, if allowed.
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto, Face face, int userId) {
+        public AuthenticationResult(CryptoObject crypto, Face face, int userId,
+                boolean isStrongBiometric) {
             mCryptoObject = crypto;
             mFace = face;
             mUserId = userId;
+            mIsStrongBiometric = isStrongBiometric;
         }
 
         /**
@@ -857,6 +862,16 @@
         public int getUserId() {
             return mUserId;
         }
+
+        /**
+         * Check whether the strength of the face modality associated with this operation is strong
+         * (i.e. not weak or convenience).
+         *
+         * @hide
+         */
+        public boolean isStrongBiometric() {
+            return mIsStrongBiometric;
+        }
     }
 
     /**
@@ -1056,7 +1071,8 @@
                             msg.arg2 /* vendorCode */);
                     break;
                 case MSG_AUTHENTICATION_SUCCEEDED:
-                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */);
+                    sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+                            msg.arg2 == 1 /* isStrongBiometric */);
                     break;
                 case MSG_AUTHENTICATION_FAILED:
                     sendAuthenticatedFailed();
@@ -1133,10 +1149,10 @@
         }
     }
 
-    private void sendAuthenticatedSucceeded(Face face, int userId) {
+    private void sendAuthenticatedSucceeded(Face face, int userId, boolean isStrongBiometric) {
         if (mAuthenticationCallback != null) {
             final AuthenticationResult result =
-                    new AuthenticationResult(mCryptoObject, face, userId);
+                    new AuthenticationResult(mCryptoObject, face, userId, isStrongBiometric);
             mAuthenticationCallback.onAuthenticationSucceeded(result);
         }
     }
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 8ba2473..6318274 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -110,4 +110,7 @@
     void getFeature(int userId, int feature, IFaceServiceReceiver receiver, String opPackageName);
 
     void userActivity();
+
+    // Initialize the OEM configured biometric strength
+    void initConfiguredStrength(int strength);
 }
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index 10f9c43..7582308 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -24,7 +24,8 @@
 oneway interface IFaceServiceReceiver {
     void onEnrollResult(long deviceId, int faceId, int remaining);
     void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
-    void onAuthenticationSucceeded(long deviceId, in Face face, int userId);
+    void onAuthenticationSucceeded(long deviceId, in Face face, int userId,
+            boolean isStrongBiometric);
     void onAuthenticationFailed(long deviceId);
     void onError(long deviceId, int error, int vendorCode);
     void onRemoved(long deviceId, int faceId, int remaining);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index f301a5c..dc8ea5e 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -176,6 +176,7 @@
         private Fingerprint mFingerprint;
         private CryptoObject mCryptoObject;
         private int mUserId;
+        private boolean mIsStrongBiometric;
 
         /**
          * Authentication result
@@ -184,10 +185,12 @@
          * @param fingerprint the recognized fingerprint data, if allowed.
          * @hide
          */
-        public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId) {
+        public AuthenticationResult(CryptoObject crypto, Fingerprint fingerprint, int userId,
+                boolean isStrongBiometric) {
             mCryptoObject = crypto;
             mFingerprint = fingerprint;
             mUserId = userId;
+            mIsStrongBiometric = isStrongBiometric;
         }
 
         /**
@@ -211,6 +214,15 @@
          * @hide
          */
         public int getUserId() { return mUserId; }
+
+        /**
+         * Check whether the strength of the fingerprint modality associated with this operation is
+         * strong (i.e. not weak or convenience).
+         * @hide
+         */
+        public boolean isStrongBiometric() {
+            return mIsStrongBiometric;
+        }
     };
 
     /**
@@ -833,7 +845,8 @@
                             msg.arg2 /* vendorCode */);
                     break;
                 case MSG_AUTHENTICATION_SUCCEEDED:
-                    sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */);
+                    sendAuthenticatedSucceeded((Fingerprint) msg.obj, msg.arg1 /* userId */,
+                            msg.arg2 == 1 /* isStrongBiometric */);
                     break;
                 case MSG_AUTHENTICATION_FAILED:
                     sendAuthenticatedFailed();
@@ -890,10 +903,10 @@
         }
     }
 
-    private void sendAuthenticatedSucceeded(Fingerprint fp, int userId) {
+    private void sendAuthenticatedSucceeded(Fingerprint fp, int userId, boolean isStrongBiometric) {
         if (mAuthenticationCallback != null) {
             final AuthenticationResult result =
-                    new AuthenticationResult(mCryptoObject, fp, userId);
+                    new AuthenticationResult(mCryptoObject, fp, userId, isStrongBiometric);
             mAuthenticationCallback.onAuthenticationSucceeded(result);
         }
     }
@@ -1078,8 +1091,10 @@
         }
 
         @Override // binder call
-        public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId) {
-            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, 0, fp).sendToTarget();
+        public void onAuthenticationSucceeded(long deviceId, Fingerprint fp, int userId,
+                boolean isStrongBiometric) {
+            mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId, isStrongBiometric ? 1 : 0,
+                    fp).sendToTarget();
         }
 
         @Override // binder call
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index f2ffd08d..0285448 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -113,4 +113,7 @@
 
     // Removes a callback set by addClientActiveCallback
     void removeClientActiveCallback(IFingerprintClientActiveCallback callback);
+
+    // Initialize the OEM configured biometric strength
+    void initConfiguredStrength(int strength);
 }
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index cf1c94e..4412cee 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -24,7 +24,8 @@
 oneway interface IFingerprintServiceReceiver {
     void onEnrollResult(long deviceId, int fingerId, int groupId, int remaining);
     void onAcquired(long deviceId, int acquiredInfo, int vendorCode);
-    void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId);
+    void onAuthenticationSucceeded(long deviceId, in Fingerprint fp, int userId,
+            boolean isStrongBiometric);
     void onAuthenticationFailed(long deviceId);
     void onError(long deviceId, int error, int vendorCode);
     void onRemoved(long deviceId, int fingerId, int groupId, int remaining);
diff --git a/core/java/android/hardware/iris/IIrisService.aidl b/core/java/android/hardware/iris/IIrisService.aidl
index 8cf3c13..5ef0a0c 100644
--- a/core/java/android/hardware/iris/IIrisService.aidl
+++ b/core/java/android/hardware/iris/IIrisService.aidl
@@ -21,4 +21,6 @@
  * @hide
  */
 interface IIrisService {
-}
\ No newline at end of file
+    // Initialize the OEM configured biometric strength
+    void initConfiguredStrength(int strength);
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 84fd580..0f2060a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -63,6 +63,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Manages users and user details on a multi-user system. There are two major categories of
@@ -2717,10 +2718,11 @@
             Manifest.permission.CREATE_USERS})
     @UserHandleAware
     public @Nullable UserHandle createProfile(@NonNull String name, @NonNull String userType,
-            @Nullable String[] disallowedPackages) throws UserOperationException {
+            @NonNull Set<String> disallowedPackages) throws UserOperationException {
         try {
             return mService.createProfileForUserWithThrow(name, userType, 0,
-                    mUserId, disallowedPackages).getUserHandle();
+                    mUserId, disallowedPackages.toArray(
+                            new String[disallowedPackages.size()])).getUserHandle();
         } catch (ServiceSpecificException e) {
             return returnNullOrThrowUserOperationException(e,
                     mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R);
@@ -3354,19 +3356,46 @@
     }
 
     /**
-     * Returns a list of ids for profiles associated with the context user including the user
-     * itself.
+     * Returns a list of ids for enabled profiles associated with the context user including the
+     * user itself.
      *
-     * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
      * @return A non-empty list of UserHandles associated with the calling user.
-     *
      * @hide
      */
     @SystemApi
     @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS}, conditional = true)
     @UserHandleAware
-    public @NonNull List<UserHandle> getUserProfiles(boolean enabledOnly) {
+    public @NonNull List<UserHandle> getEnabledProfiles() {
+        return getProfiles(true);
+    }
+
+    /**
+     * Returns a list of ids for all profiles associated with the context user including the user
+     * itself.
+     *
+     * @return A non-empty list of UserHandles associated with the calling user.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
+    @UserHandleAware
+    public @NonNull List<UserHandle> getAllProfiles() {
+        return getProfiles(false);
+    }
+
+    /**
+     * Returns a list of ids for profiles associated with the context user including the user
+     * itself.
+     *
+     * @param enabledOnly whether to return only {@link UserInfo#isEnabled() enabled} profiles
+     * @return A non-empty list of UserHandles associated with the calling user.
+     */
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS}, conditional = true)
+    @UserHandleAware
+    private @NonNull List<UserHandle> getProfiles(boolean enabledOnly) {
         final int[] userIds = getProfileIds(mUserId, enabledOnly);
         final List<UserHandle> result = new ArrayList<>(userIds.length);
         for (int userId : userIds) {
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index c9dc05b..ead4e46 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 
@@ -62,7 +64,7 @@
         private Exception mLastWtf;
 
         // Layout of event log entry received from Android logger.
-        //  see system/core/include/log/log.h
+        //  see system/core/liblog/include/log/log_read.h
         private static final int LENGTH_OFFSET = 0;
         private static final int HEADER_SIZE_OFFSET = 2;
         private static final int PROCESS_OFFSET = 4;
@@ -73,7 +75,7 @@
 
         // Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
         private static final int V1_PAYLOAD_START = 20;
-        private static final int DATA_OFFSET = 4;
+        private static final int TAG_LENGTH = 4;
 
         // Value types
         private static final byte INT_TYPE    = 0;
@@ -121,26 +123,26 @@
 
         /** @return the type tag code of the entry */
         public int getTag() {
-            int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
-            if (offset == 0) {
-                offset = V1_PAYLOAD_START;
-            }
-            return mBuffer.getInt(offset);
+            return mBuffer.getInt(getHeaderSize());
         }
 
+        private int getHeaderSize() {
+            int length = mBuffer.getShort(HEADER_SIZE_OFFSET);
+            if (length != 0) {
+                return length;
+            }
+            return V1_PAYLOAD_START;
+        }
         /** @return one of Integer, Long, Float, String, null, or Object[] of same. */
         public synchronized Object getData() {
             try {
-                int offset = mBuffer.getShort(HEADER_SIZE_OFFSET);
-                if (offset == 0) {
-                    offset = V1_PAYLOAD_START;
-                }
+                int offset = getHeaderSize();
                 mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
-                if ((offset + DATA_OFFSET) >= mBuffer.limit()) {
+                if ((offset + TAG_LENGTH) >= mBuffer.limit()) {
                     // no payload
                     return null;
                 }
-                mBuffer.position(offset + DATA_OFFSET); // Just after the tag.
+                mBuffer.position(offset + TAG_LENGTH); // Just after the tag.
                 return decodeObject();
             } catch (IllegalArgumentException e) {
                 Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
@@ -153,6 +155,28 @@
             }
         }
 
+        /**
+         * Construct a new EventLog object from the current object, copying all log metadata
+         * but replacing the actual payload with the content provided.
+         * @hide
+         */
+        public Event withNewData(@Nullable Object object) {
+            byte[] payload = encodeObject(object);
+            if (payload.length > 65535 - TAG_LENGTH) {
+                throw new IllegalArgumentException("Payload too long");
+            }
+            int headerLength = getHeaderSize();
+            byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length];
+            // Copy header (including the 4 bytes of tag integer at the beginning of payload)
+            System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH);
+            // Fill in encoded objects
+            System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length);
+            Event result = new Event(newBytes);
+            // Patch payload length in header
+            result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH));
+            return result;
+        }
+
         /** @return the loggable item at the current position in mBuffer. */
         private Object decodeObject() {
             byte type = mBuffer.get();
@@ -190,6 +214,66 @@
             }
         }
 
+        private static @NonNull byte[] encodeObject(@Nullable Object object) {
+            if (object == null) {
+                return new byte[0];
+            }
+            if (object instanceof Integer) {
+                return ByteBuffer.allocate(1 + 4)
+                        .order(ByteOrder.nativeOrder())
+                        .put(INT_TYPE)
+                        .putInt((Integer) object)
+                        .array();
+            } else if (object instanceof Long) {
+                return ByteBuffer.allocate(1 + 8)
+                        .order(ByteOrder.nativeOrder())
+                        .put(LONG_TYPE)
+                        .putLong((Long) object)
+                        .array();
+            } else if (object instanceof Float) {
+                return ByteBuffer.allocate(1 + 4)
+                        .order(ByteOrder.nativeOrder())
+                        .put(FLOAT_TYPE)
+                        .putFloat((Float) object)
+                        .array();
+            } else if (object instanceof String) {
+                String string = (String) object;
+                byte[] bytes;
+                try {
+                    bytes = string.getBytes("UTF-8");
+                } catch (UnsupportedEncodingException e) {
+                    bytes = new byte[0];
+                }
+                return ByteBuffer.allocate(1 + 4 + bytes.length)
+                         .order(ByteOrder.nativeOrder())
+                         .put(STRING_TYPE)
+                         .putInt(bytes.length)
+                         .put(bytes)
+                         .array();
+            } else if (object instanceof Object[]) {
+                Object[] objects = (Object[]) object;
+                if (objects.length > 255) {
+                    throw new IllegalArgumentException("Object array too long");
+                }
+                byte[][] bytes = new byte[objects.length][];
+                int totalLength = 0;
+                for (int i = 0; i < objects.length; i++) {
+                    bytes[i] = encodeObject(objects[i]);
+                    totalLength += bytes[i].length;
+                }
+                ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength)
+                        .order(ByteOrder.nativeOrder())
+                        .put(LIST_TYPE)
+                        .put((byte) objects.length);
+                for (int i = 0; i < objects.length; i++) {
+                    buffer.put(bytes[i]);
+                }
+                return buffer.array();
+            } else {
+                throw new IllegalArgumentException("Unknown object type " + object);
+            }
+        }
+
         /** @hide */
         public static Event fromBytes(byte[] data) {
             return new Event(data);
diff --git a/core/java/com/android/internal/app/ResolverViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java
index 84fed9c..8239018 100644
--- a/core/java/com/android/internal/app/ResolverViewPager.java
+++ b/core/java/com/android/internal/app/ResolverViewPager.java
@@ -24,10 +24,10 @@
 import com.android.internal.widget.ViewPager;
 
 /**
- * A {@link ViewPager} which wraps around its first child's height and has swiping disabled.
+ * A {@link ViewPager} which wraps around its first child's height.
  * <p>Normally {@link ViewPager} instances expand their height to cover all remaining space in
  * the layout.
- * <p>This class is used for the intent resolver and share sheet's tabbed view.
+ * <p>This class is used for the intent resolver's tabbed view.
  */
 public class ResolverViewPager extends ViewPager {
 
@@ -70,14 +70,4 @@
         heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return false;
-    }
 }
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index ffa347c..72f16e4 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -186,13 +186,6 @@
      */
     @NonNull
     public static OverlayConfig getZygoteInstance() {
-        if (Process.myUid() != Process.ROOT_UID) {
-            // Scan the overlays in the zygote process to generate configuration settings for
-            // overlays on the system image. Do not cache this instance so OverlayConfig will not
-            // be present in applications by default.
-            throw new IllegalStateException("Can only be invoked in the root process");
-        }
-
         Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance");
         try {
             return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new,
@@ -209,13 +202,12 @@
      */
     @NonNull
     public static OverlayConfig initializeSystemInstance(PackageProvider packageProvider) {
-        if (Process.myUid() != Process.SYSTEM_UID) {
-            throw new IllegalStateException("Can only be invoked in the system process");
-        }
-
         Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance");
-        sInstance = new OverlayConfig(null, null, packageProvider);
-        Trace.traceEnd(Trace.TRACE_TAG_RRO);
+        try {
+            sInstance = new OverlayConfig(null, null, packageProvider);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RRO);
+        }
         return sInstance;
     }
 
@@ -373,10 +365,6 @@
      */
     @NonNull
     public String[] createImmutableFrameworkIdmapsInZygote() {
-        if (Process.myUid() != Process.ROOT_UID) {
-            throw new IllegalStateException("This method can only be called from the root process");
-        }
-
         final String targetPath = "/system/framework/framework-res.apk";
         final ArrayList<String> idmapPaths = new ArrayList<>();
         final ArrayList<IdmapInvocation> idmapInvocations =
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index e24e982..e35fda1 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -57,6 +57,8 @@
     void registerStrongAuthTracker(in IStrongAuthTracker tracker);
     void unregisterStrongAuthTracker(in IStrongAuthTracker tracker);
     void requireStrongAuth(int strongAuthReason, int userId);
+    void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId);
+    void scheduleNonStrongBiometricIdleTimeout(int userId);
     void systemReady();
     void userPresent(int userId);
     int getStrongAuthForUser(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 864429c..d9b2902 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -49,6 +49,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
 
@@ -1382,6 +1383,22 @@
         }
     }
 
+    public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        try {
+            getLockSettings().reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not report successful biometric unlock", e);
+        }
+    }
+
+    public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+        try {
+            getLockSettings().scheduleNonStrongBiometricIdleTimeout(userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not schedule non-strong biometric idle timeout", e);
+        }
+    }
+
     /**
      * @see StrongAuthTracker#getStrongAuthForUser
      */
@@ -1557,7 +1574,8 @@
                         SOME_AUTH_REQUIRED_AFTER_USER_REQUEST,
                         STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
                         STRONG_AUTH_REQUIRED_AFTER_TIMEOUT,
-                        STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN})
+                        STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN,
+                        STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT})
         @Retention(RetentionPolicy.SOURCE)
         public @interface StrongAuthFlags {}
 
@@ -1604,6 +1622,12 @@
         public static final int STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE = 0x40;
 
         /**
+         * Strong authentication is required because it hasn't been used for a time after a
+         * non-strong biometric (i.e. weak or convenience biometric) is used to unlock device.
+         */
+        public static final int STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT = 0x80;
+
+        /**
          * Strong auth flags that do not prevent biometric methods from being accepted as auth.
          * If any other flags are set, biometric authentication is disabled.
          */
@@ -1614,6 +1638,10 @@
         private final H mHandler;
         private final int mDefaultStrongAuthFlags;
 
+        private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser =
+                new SparseBooleanArray();
+        private final boolean mDefaultIsNonStrongBiometricAllowed = true;
+
         public StrongAuthTracker(Context context) {
             this(context, Looper.myLooper());
         }
@@ -1657,8 +1685,21 @@
          * @return true if unlocking with a biometric method alone is allowed for {@code userId}
          * by the current strong authentication requirements.
          */
-        public boolean isBiometricAllowedForUser(int userId) {
-            return (getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0;
+        public boolean isBiometricAllowedForUser(boolean isStrongBiometric, int userId) {
+            boolean allowed = ((getStrongAuthForUser(userId) & ~ALLOWING_BIOMETRIC) == 0);
+            if (!isStrongBiometric) {
+                allowed &= isNonStrongBiometricAllowedAfterIdleTimeout(userId);
+            }
+            return allowed;
+        }
+
+        /**
+         * @return true if unlocking with a non-strong (i.e. weak or convenience) biometric method
+         * alone is allowed for {@code userId}, otherwise returns false.
+         */
+        public boolean isNonStrongBiometricAllowedAfterIdleTimeout(int userId) {
+            return mIsNonStrongBiometricAllowedForUser.get(userId,
+                    mDefaultIsNonStrongBiometricAllowed);
         }
 
         /**
@@ -1667,6 +1708,12 @@
         public void onStrongAuthRequiredChanged(int userId) {
         }
 
+        /**
+         * Called when whether non-strong biometric is allowed for {@code userId} changed.
+         */
+        public void onIsNonStrongBiometricAllowedChanged(int userId) {
+        }
+
         protected void handleStrongAuthRequiredChanged(@StrongAuthFlags int strongAuthFlags,
                 int userId) {
             int oldValue = getStrongAuthForUser(userId);
@@ -1680,6 +1727,18 @@
             }
         }
 
+        protected void handleIsNonStrongBiometricAllowedChanged(boolean allowed,
+                int userId) {
+            boolean oldValue = isNonStrongBiometricAllowedAfterIdleTimeout(userId);
+            if (allowed != oldValue) {
+                if (allowed == mDefaultIsNonStrongBiometricAllowed) {
+                    mIsNonStrongBiometricAllowedForUser.delete(userId);
+                } else {
+                    mIsNonStrongBiometricAllowedForUser.put(userId, allowed);
+                }
+                onIsNonStrongBiometricAllowedChanged(userId);
+            }
+        }
 
         protected final IStrongAuthTracker.Stub mStub = new IStrongAuthTracker.Stub() {
             @Override
@@ -1688,10 +1747,17 @@
                 mHandler.obtainMessage(H.MSG_ON_STRONG_AUTH_REQUIRED_CHANGED,
                         strongAuthFlags, userId).sendToTarget();
             }
+
+            @Override
+            public void onIsNonStrongBiometricAllowedChanged(boolean allowed, int userId) {
+                mHandler.obtainMessage(H.MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED,
+                        allowed ? 1 : 0, userId).sendToTarget();
+            }
         };
 
         private class H extends Handler {
             static final int MSG_ON_STRONG_AUTH_REQUIRED_CHANGED = 1;
+            static final int MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED = 2;
 
             public H(Looper looper) {
                 super(looper);
@@ -1703,6 +1769,10 @@
                     case MSG_ON_STRONG_AUTH_REQUIRED_CHANGED:
                         handleStrongAuthRequiredChanged(msg.arg1, msg.arg2);
                         break;
+                    case MSG_ON_IS_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED:
+                        handleIsNonStrongBiometricAllowedChanged(msg.arg1 == 1 /* allowed */,
+                                msg.arg2);
+                        break;
                 }
             }
         }
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index c0de693..5676049 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -90,7 +90,7 @@
                 android:id="@android:id/tabcontent"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
-                <com.android.internal.app.ResolverViewPager
+                <com.android.internal.widget.ViewPager
                     android:id="@+id/profile_pager"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"/>
diff --git a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
index 9f0af60..f0e70a5 100644
--- a/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
+++ b/core/tests/coretests/src/android/accessibilityservice/AccessibilityShortcutInfoTest.java
@@ -92,7 +92,7 @@
     @Test
     public void testLoadAnimatedImage() {
         assertNotNull("Can't find animated image",
-                mShortcutInfo.loadAnimatedImage(mPackageManager));
+                mShortcutInfo.loadAnimatedImage(mTargetContext));
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/util/EventLogTest.java b/core/tests/coretests/src/android/util/EventLogTest.java
new file mode 100644
index 0000000..94e72c4
--- /dev/null
+++ b/core/tests/coretests/src/android/util/EventLogTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.EventLog.Event;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Unit tests for {@link android.util.EventLog} */
+public class EventLogTest {
+
+    @Test
+    public void testWithNewData() throws Throwable {
+        Event event = createEvent(() -> {
+            EventLog.writeEvent(314,  123);
+        }, 314);
+
+        assertTrue(event.withNewData(12345678L).getData().equals(12345678L));
+        assertTrue(event.withNewData(2.718f).getData().equals(2.718f));
+        assertTrue(event.withNewData("test string").getData().equals("test string"));
+
+        Object[] objects = ((Object[]) event.withNewData(
+                new Object[] {111, 2.22f, 333L, "444"}).getData());
+        assertEquals(4, objects.length);
+        assertTrue(objects[0].equals(111));
+        assertTrue(objects[1].equals(2.22f));
+        assertTrue(objects[2].equals(333L));
+        assertTrue(objects[3].equals("444"));
+    }
+
+    /**
+     * Creates an Event object. Only the native code has the serialization and deserialization logic
+     * so need to actually emit a real log in order to generate the object.
+     */
+    private Event createEvent(Runnable generator, int expectedTag) throws Exception {
+        Long markerData = System.currentTimeMillis();
+        EventLog.writeEvent(expectedTag, markerData);
+        generator.run();
+
+        List<Event> events = new ArrayList<>();
+        // Give the message some time to show up in the log
+        Thread.sleep(20);
+        EventLog.readEvents(new int[] {expectedTag}, events);
+        for (int i = 0; i < events.size() - 1; i++) {
+            if (markerData.equals(events.get(i).getData())) {
+                return events.get(i + 1);
+            }
+        }
+        throw new AssertionFailedError("Unable to locate marker event");
+    }
+}
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index c12159c..5858e39 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -280,7 +280,10 @@
      * {@link android.os.Build.VERSION_CODES#Q}, it is no longer required to be
      * convex.
      *
-     * @deprecated The path is no longer required to be convex. Use {@link #setPath} instead.
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, the restriction
+     * that the path must be convex is removed. However, the API is misnamed until
+     * {@link android.os.Build.VERSION_CODES#R}, when {@link #setPath} is
+     * introduced. Use {@link #setPath} instead.
      */
     @Deprecated
     public void setConvexPath(@NonNull Path convexPath) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 0b825f6..383202b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1654,6 +1654,12 @@
      * @hide
      * Interface to be notified of changes in the preferred audio device set for a given audio
      * strategy.
+     * <p>Note that this listener will only be invoked whenever
+     * {@link #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)} or
+     * {@link #removePreferredDeviceForStrategy(AudioProductStrategy)} causes a change in
+     * preferred device. It will not be invoked directly after registration with
+     * {@link #addOnPreferredDeviceForStrategyChangedListener(Executor, OnPreferredDeviceForStrategyChangedListener)}
+     * to indicate which strategies had preferred devices at the time of registration.</p>
      * @see #setPreferredDeviceForStrategy(AudioProductStrategy, AudioDeviceAttributes)
      * @see #removePreferredDeviceForStrategy(AudioProductStrategy)
      * @see #getPreferredDeviceForStrategy(AudioProductStrategy)
diff --git a/media/java/android/media/IMediaRoute2ProviderService.aidl b/media/java/android/media/IMediaRoute2ProviderService.aidl
index cd0def3..6cd2547 100644
--- a/media/java/android/media/IMediaRoute2ProviderService.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderService.aidl
@@ -29,13 +29,13 @@
     // MediaRoute2ProviderService#MediaRoute2ProviderServiceStub for readability.
     void setCallback(IMediaRoute2ProviderServiceCallback callback);
     void updateDiscoveryPreference(in RouteDiscoveryPreference discoveryPreference);
-    void setRouteVolume(String routeId, int volume);
+    void setRouteVolume(String routeId, int volume, long requestId);
 
     void requestCreateSession(String packageName, String routeId, long requestId,
             in @nullable Bundle sessionHints);
-    void selectRoute(String sessionId, String routeId);
-    void deselectRoute(String sessionId, String routeId);
-    void transferToRoute(String sessionId, String routeId);
-    void setSessionVolume(String sessionId, int volume);
-    void releaseSession(String sessionId);
+    void selectRoute(String sessionId, String routeId, long requestId);
+    void deselectRoute(String sessionId, String routeId, long requestId);
+    void transferToRoute(String sessionId, String routeId, long requestId);
+    void setSessionVolume(String sessionId, int volume, long requestId);
+    void releaseSession(String sessionId, long requestId);
 }
diff --git a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
index e35b0c4..ab42d75 100644
--- a/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderServiceCallback.aidl
@@ -31,4 +31,5 @@
     void notifySessionCreationFailed(long requestId);
     void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
     void notifySessionReleased(in RoutingSessionInfo sessionInfo);
+    void notifyRequestFailed(long requestId, int reason);
 }
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index ffad659..a2f9ee9 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -30,4 +30,5 @@
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
     void notifyRoutesChanged(in List<MediaRoute2Info> routes);
+    void notifyRequestFailed(int requestId, int reason);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index cbec323..c7cb07d 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -71,16 +71,17 @@
     void registerManager(IMediaRouter2Manager manager, String packageName);
     void unregisterManager(IMediaRouter2Manager manager);
     void setRouteVolumeWithManager(IMediaRouter2Manager manager, in MediaRoute2Info route,
-            int volume);
+            int volume, int requestId);
 
     void requestCreateSessionWithManager(IMediaRouter2Manager manager, String packageName,
             in @nullable MediaRoute2Info route, int requestId);
     void selectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
-            in MediaRoute2Info route);
+            in MediaRoute2Info route, int requestId);
     void deselectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
-            in MediaRoute2Info route);
+            in MediaRoute2Info route, int requestId);
     void transferToRouteWithManager(IMediaRouter2Manager manager, String sessionId,
-            in MediaRoute2Info route);
-    void setSessionVolumeWithManager(IMediaRouter2Manager manager, String sessionId, int volume);
-    void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId);
+            in MediaRoute2Info route, int requestId);
+    void setSessionVolumeWithManager(IMediaRouter2Manager manager, String sessionId, int volume,
+            int requestId);
+    void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId, int requestId);
 }
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 38233fd..aa0eda1 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -19,6 +19,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.CallSuper;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SdkConstant;
@@ -37,6 +38,8 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -51,7 +54,7 @@
  * Media apps which use {@link MediaRouter2} can request to play their media on the routes.
  * </p><p>
  * When {@link MediaRouter2 media router} wants to play media on a route,
- * {@link #onCreateSession(String, String, long, Bundle)} will be called to handle the request.
+ * {@link #onCreateSession(long, String, String, Bundle)} will be called to handle the request.
  * A session can be considered as a group of currently selected routes for each connection.
  * Create and manage the sessions by yourself, and notify the {@link RoutingSessionInfo
  * session infos} when there are any changes.
@@ -61,6 +64,8 @@
  * a {@link MediaRouter2 media router} by an application. See
  * {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} for the details.
  * </p>
+ * Use {@link #notifyRequestFailed(long, int)} to notify the failure with previously received
+ * request ID.
  */
 public abstract class MediaRoute2ProviderService extends Service {
     private static final String TAG = "MR2ProviderService";
@@ -79,7 +84,53 @@
      *
      * @see #notifySessionCreated(RoutingSessionInfo, long)
      */
-    public static final long REQUEST_ID_UNKNOWN = 0;
+    public static final long REQUEST_ID_NONE = 0;
+
+    /**
+     * The request has failed due to unknown reason.
+     *
+     * @see #notifyRequestFailed(long, int)
+     */
+    public static final int REASON_UNKNOWN_ERROR = 0;
+
+    /**
+     * The request has failed since this service rejected the request.
+     *
+     * @see #notifyRequestFailed(long, int)
+     */
+    public static final int REASON_REJECTED = 1;
+
+    /**
+     * The request has failed due to a network error.
+     *
+     * @see #notifyRequestFailed(long, int)
+     */
+    public static final int REASON_NETWORK_ERROR = 2;
+
+    /**
+     * The request has failed since the requested route is no longer available.
+     *
+     * @see #notifyRequestFailed(long, int)
+     */
+    public static final int REASON_ROUTE_NOT_AVAILABLE = 3;
+
+    /**
+     * The request has failed since the request is not valid. For example, selecting a route
+     * which is not selectable.
+     *
+     * @see #notifyRequestFailed(long, int)
+     */
+    public static final int REASON_INVALID_COMMAND = 4;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = "REASON_", value = {
+            REASON_UNKNOWN_ERROR, REASON_REJECTED, REASON_NETWORK_ERROR, REASON_ROUTE_NOT_AVAILABLE,
+            REASON_INVALID_COMMAND
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Reason {}
 
     private final Handler mHandler;
     private final Object mSessionLock = new Object();
@@ -116,20 +167,23 @@
     /**
      * Called when a volume setting is requested on a route of the provider
      *
+     * @param requestId the id of this request
      * @param routeId the id of the route
      * @param volume the target volume
-     * @see MediaRoute2Info#getVolumeMax()
+     * @see MediaRoute2Info.Builder#setVolume(int)
      */
-    public abstract void onSetRouteVolume(@NonNull String routeId, int volume);
+    public abstract void onSetRouteVolume(long requestId, @NonNull String routeId, int volume);
 
     /**
      * Called when {@link MediaRouter2.RoutingController#setVolume(int)} is called on
      * a routing session of the provider
      *
+     * @param requestId the id of this request
      * @param sessionId the id of the routing session
      * @param volume the target volume
+     * @see RoutingSessionInfo.Builder#setVolume(int)
      */
-    public abstract void onSetSessionVolume(@NonNull String sessionId, int volume);
+    public abstract void onSetSessionVolume(long requestId, @NonNull String sessionId, int volume);
 
     /**
      * Gets information of the session with the given id.
@@ -161,14 +215,15 @@
     /**
      * Notifies clients of that the session is created and ready for use.
      * <p>
-     * If this session is created without any creation request, use {@link #REQUEST_ID_UNKNOWN}
+     * If this session is created without any creation request, use {@link #REQUEST_ID_NONE}
      * as the request ID.
      *
      * @param sessionInfo information of the new session.
      *                    The {@link RoutingSessionInfo#getId() id} of the session must be unique.
      * @param requestId id of the previous request to create this session provided in
-     *                  {@link #onCreateSession(String, String, long, Bundle)}
-     * @see #onCreateSession(String, String, long, Bundle)
+     *                  {@link #onCreateSession(long, String, String, Bundle)}. Can be
+     *                  {@link #REQUEST_ID_NONE} if this session is created without any request.
+     * @see #onCreateSession(long, String, String, Bundle)
      * @see #getSessionInfo(String)
      */
     public final void notifySessionCreated(@NonNull RoutingSessionInfo sessionInfo,
@@ -201,8 +256,8 @@
      * Notifies clients of that the session could not be created.
      *
      * @param requestId id of the previous request to create the session provided in
-     *                  {@link #onCreateSession(String, String, long, Bundle)}.
-     * @see #onCreateSession(String, String, long, Bundle)
+     *                  {@link #onCreateSession(long, String, String, Bundle)}.
+     * @see #onCreateSession(long, String, String, Bundle)
      */
     public final void notifySessionCreationFailed(long requestId) {
         if (mRemoteCallback == null) {
@@ -246,7 +301,7 @@
      * Notifies that the session is released.
      *
      * @param sessionId id of the released session.
-     * @see #onReleaseSession(String)
+     * @see #onReleaseSession(long, String)
      */
     public final void notifySessionReleased(@NonNull String sessionId) {
         if (TextUtils.isEmpty(sessionId)) {
@@ -273,6 +328,29 @@
     }
 
     /**
+     * Notifies to the client that the request has failed.
+     *
+     * @param requestId the ID of the previous request
+     * @param reason the reason why the request has failed
+     *
+     * @see #REASON_UNKNOWN_ERROR
+     * @see #REASON_REJECTED
+     * @see #REASON_NETWORK_ERROR
+     * @see #REASON_ROUTE_NOT_AVAILABLE
+     * @see #REASON_INVALID_COMMAND
+     */
+    public final void notifyRequestFailed(long requestId, @Reason int reason) {
+        if (mRemoteCallback == null) {
+            return;
+        }
+        try {
+            mRemoteCallback.notifyRequestFailed(requestId, reason);
+        } catch (RemoteException ex) {
+            Log.w(TAG, "Failed to notify that the request has failed.");
+        }
+    }
+
+    /**
      * Called when the service receives a request to create a session.
      * <p>
      * You should create and maintain your own session and notifies the client of
@@ -288,9 +366,9 @@
      * If you can't create the session or want to reject the request, call
      * {@link #notifySessionCreationFailed(long)} with the given {@code requestId}.
      *
+     * @param requestId the id of this request
      * @param packageName the package name of the application that selected the route
      * @param routeId the id of the route initially being connected
-     * @param requestId the id of this session creation request
      * @param sessionHints an optional bundle of app-specific arguments sent by
      *                     {@link MediaRouter2}, or null if none. The contents of this bundle
      *                     may affect the result of session creation.
@@ -299,8 +377,8 @@
      * @see RoutingSessionInfo.Builder#addSelectedRoute(String)
      * @see RoutingSessionInfo.Builder#setControlHints(Bundle)
      */
-    public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
-            long requestId, @Nullable Bundle sessionHints);
+    public abstract void onCreateSession(long requestId, @NonNull String packageName,
+            @NonNull String routeId, @Nullable Bundle sessionHints);
 
     /**
      * Called when the session should be released. A client of the session or system can request
@@ -312,44 +390,48 @@
      * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger
      * this method to be called.
      *
+     * @param requestId the id of this request
      * @param sessionId id of the session being released.
      * @see #notifySessionReleased(String)
      * @see #getSessionInfo(String)
      */
-    public abstract void onReleaseSession(@NonNull String sessionId);
+    public abstract void onReleaseSession(long requestId, @NonNull String sessionId);
 
-    //TODO: make a way to reject the request
     /**
      * Called when a client requests selecting a route for the session.
      * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
      * to update session info.
      *
+     * @param requestId the id of this request
      * @param sessionId id of the session
      * @param routeId id of the route
      */
-    public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
+    public abstract void onSelectRoute(long requestId, @NonNull String sessionId,
+            @NonNull String routeId);
 
-    //TODO: make a way to reject the request
     /**
      * Called when a client requests deselecting a route from the session.
      * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
      * to update session info.
      *
+     * @param requestId the id of this request
      * @param sessionId id of the session
      * @param routeId id of the route
      */
-    public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
+    public abstract void onDeselectRoute(long requestId, @NonNull String sessionId,
+            @NonNull String routeId);
 
-    //TODO: make a way to reject the request
     /**
      * Called when a client requests transferring a session to a route.
      * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)}
      * to update session info.
      *
+     * @param requestId the id of this request
      * @param sessionId id of the session
      * @param routeId id of the route
      */
-    public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
+    public abstract void onTransferToRoute(long requestId, @NonNull String sessionId,
+            @NonNull String routeId);
 
     /**
      * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
@@ -439,12 +521,12 @@
         }
 
         @Override
-        public void setRouteVolume(String routeId, int volume) {
+        public void setRouteVolume(String routeId, int volume, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetRouteVolume,
-                    MediaRoute2ProviderService.this, routeId, volume));
+                    MediaRoute2ProviderService.this, requestId, routeId, volume));
         }
 
         @Override
@@ -454,12 +536,12 @@
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
-                    MediaRoute2ProviderService.this, packageName, routeId, requestId,
+                    MediaRoute2ProviderService.this, requestId, packageName, routeId,
                     requestCreateSession));
         }
 
         @Override
-        public void selectRoute(@NonNull String sessionId, String routeId) {
+        public void selectRoute(String sessionId, String routeId, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
@@ -468,11 +550,11 @@
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
-                    MediaRoute2ProviderService.this, sessionId, routeId));
+                    MediaRoute2ProviderService.this, requestId, sessionId, routeId));
         }
 
         @Override
-        public void deselectRoute(@NonNull String sessionId, String routeId) {
+        public void deselectRoute(String sessionId, String routeId, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
@@ -481,11 +563,11 @@
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
-                    MediaRoute2ProviderService.this, sessionId, routeId));
+                    MediaRoute2ProviderService.this, requestId, sessionId, routeId));
         }
 
         @Override
-        public void transferToRoute(@NonNull String sessionId, String routeId) {
+        public void transferToRoute(String sessionId, String routeId, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
@@ -494,20 +576,20 @@
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
-                    MediaRoute2ProviderService.this, sessionId, routeId));
+                    MediaRoute2ProviderService.this, requestId, sessionId, routeId));
         }
 
         @Override
-        public void setSessionVolume(String sessionId, int volume) {
+        public void setSessionVolume(String sessionId, int volume, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetSessionVolume,
-                    MediaRoute2ProviderService.this, sessionId, volume));
+                    MediaRoute2ProviderService.this, requestId, sessionId, volume));
         }
 
         @Override
-        public void releaseSession(@NonNull String sessionId) {
+        public void releaseSession(String sessionId, long requestId) {
             if (!checkCallerisSystem()) {
                 return;
             }
@@ -516,7 +598,7 @@
                 return;
             }
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
-                    MediaRoute2ProviderService.this, sessionId));
+                    MediaRoute2ProviderService.this, requestId, sessionId));
         }
     }
 }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 5942a3d..079fee8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -817,7 +817,7 @@
          * @return An optional bundle of app-specific arguments to send to the provider,
          *         or null if none. The contents of this bundle may affect the result of
          *         controller creation.
-         * @see MediaRoute2ProviderService#onCreateSession(String, String, long, Bundle)
+         * @see MediaRoute2ProviderService#onCreateSession(long, String, String, Bundle)
          */
         @Nullable
         Bundle onGetControllerHints(@NonNull MediaRoute2Info route);
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 636ee92..ff2c863 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -337,7 +337,8 @@
         }
         if (client != null) {
             try {
-                mMediaRouterService.setRouteVolumeWithManager(client, route, volume);
+                int requestId = mNextRequestId.getAndIncrement();
+                mMediaRouterService.setRouteVolumeWithManager(client, route, volume, requestId);
             } catch (RemoteException ex) {
                 Log.e(TAG, "Unable to send control request.", ex);
             }
@@ -368,8 +369,9 @@
         }
         if (client != null) {
             try {
+                int requestId = mNextRequestId.getAndIncrement();
                 mMediaRouterService.setSessionVolumeWithManager(
-                        client, sessionInfo.getId(), volume);
+                        client, sessionInfo.getId(), volume, requestId);
             } catch (RemoteException ex) {
                 Log.e(TAG, "Unable to send control request.", ex);
             }
@@ -443,6 +445,12 @@
         }
     }
 
+    void notifyRequestFailed(int reason) {
+        for (CallbackRecord record : mCallbackRecords) {
+            record.mExecutor.execute(() -> record.mCallback.onRequestFailed(reason));
+        }
+    }
+
     void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
         List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
         if ((prevFeatures == null && preferredFeatures.size() == 0)
@@ -593,7 +601,9 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.selectRouteWithManager(mClient, getSessionId(), route);
+                    int requestId = mNextRequestId.getAndIncrement();
+                    mMediaRouterService.selectRouteWithManager(
+                            mClient, getSessionId(), route, requestId);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to select route for session.", ex);
                 }
@@ -635,7 +645,9 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.deselectRouteWithManager(mClient, getSessionId(), route);
+                    int requestId = mNextRequestId.getAndIncrement();
+                    mMediaRouterService.deselectRouteWithManager(
+                            mClient, getSessionId(), route, requestId);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to remove route from session.", ex);
                 }
@@ -678,7 +690,9 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.transferToRouteWithManager(mClient, getSessionId(), route);
+                    int requestId = mNextRequestId.getAndIncrement();
+                    mMediaRouterService.transferToRouteWithManager(
+                            mClient, getSessionId(), route, requestId);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to transfer to route for session.", ex);
                 }
@@ -696,7 +710,9 @@
             }
             if (client != null) {
                 try {
-                    mMediaRouterService.releaseSessionWithManager(mClient, getSessionId());
+                    int requestId = mNextRequestId.getAndIncrement();
+                    mMediaRouterService.releaseSessionWithManager(
+                            mClient, getSessionId(), requestId);
                 } catch (RemoteException ex) {
                     Log.e(TAG, "Unable to notify of controller release", ex);
                 }
@@ -783,6 +799,17 @@
         public void onPreferredFeaturesChanged(@NonNull String packageName,
                 @NonNull List<String> preferredFeatures) {}
 
+        /**
+         * Called when a previous request has failed.
+         *
+         * @param reason the reason that the request has failed. Can be one of followings:
+         *               {@link MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
+         *               {@link MediaRoute2ProviderService#REASON_REJECTED},
+         *               {@link MediaRoute2ProviderService#REASON_NETWORK_ERROR},
+         *               {@link MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
+         *               {@link MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+         */
+        public void onRequestFailed(int reason) {}
     }
 
     final class CallbackRecord {
@@ -826,6 +853,13 @@
         }
 
         @Override
+        public void notifyRequestFailed(int requestId, int reason) {
+            // Note: requestId is not used.
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRequestFailed,
+                    MediaRouter2Manager.this, reason));
+        }
+
+        @Override
         public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
             mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
                     MediaRouter2Manager.this, packageName, features));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index e80562b..bcff6a1 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -19,6 +19,8 @@
 import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
 import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+import static android.media.MediaRoute2ProviderService.REASON_REJECTED;
+import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
 
 import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.FEATURE_SAMPLE;
 import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.FEATURE_SPECIAL;
@@ -32,6 +34,7 @@
 import static com.android.mediaroutertest.SampleMediaRoute2ProviderService.VOLUME_MAX;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -55,7 +58,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -69,6 +71,7 @@
 @SmallTest
 public class MediaRouter2ManagerTest {
     private static final String TAG = "MediaRouter2ManagerTest";
+    private static final int WAIT_TIME_MS = 2000;
     private static final int TIMEOUT_MS = 5000;
 
     private Context mContext;
@@ -111,6 +114,11 @@
         releaseAllSessions();
         // unregister callbacks
         clearCallbacks();
+
+        SampleMediaRoute2ProviderService instance = SampleMediaRoute2ProviderService.getInstance();
+        if (instance != null) {
+            instance.setProxy(null);
+        }
     }
 
     @Test
@@ -296,7 +304,7 @@
         String selectedSystemRouteId =
                 MediaRouter2Utils.getOriginalId(
                 mManager.getActiveSessions().get(0).getSelectedRoutes().get(0));
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(Collections.emptyList());
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
         MediaRoute2Info volRoute = routes.get(selectedSystemRouteId);
         assertNotNull(volRoute);
 
@@ -398,6 +406,53 @@
         }
     }
 
+    /**
+     * Tests that {@link android.media.MediaRoute2ProviderService#notifyRequestFailed(long, int)}
+     * should invoke the callback only when the right requestId is used.
+     */
+    @Test
+    public void testOnRequestFailedCalledForProperRequestId() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+        MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+
+        SampleMediaRoute2ProviderService instance = SampleMediaRoute2ProviderService.getInstance();
+        assertNotNull(instance);
+
+        final List<Long> requestIds = new ArrayList<>();
+        final CountDownLatch onSetRouteVolumeLatch = new CountDownLatch(1);
+        instance.setProxy(new SampleMediaRoute2ProviderService.Proxy() {
+            @Override
+            public void onSetRouteVolume(String routeId, int volume, long requestId) {
+                requestIds.add(requestId);
+                onSetRouteVolumeLatch.countDown();
+            }
+        });
+
+        addManagerCallback(new MediaRouter2Manager.Callback() {});
+        mManager.setRouteVolume(volRoute, 0);
+        assertTrue(onSetRouteVolumeLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertFalse(requestIds.isEmpty());
+
+        final int failureReason = REASON_REJECTED;
+        final CountDownLatch onRequestFailedLatch = new CountDownLatch(1);
+        addManagerCallback(new MediaRouter2Manager.Callback() {
+            @Override
+            public void onRequestFailed(int reason) {
+                if (reason == failureReason) {
+                    onRequestFailedLatch.countDown();
+                }
+            }
+        });
+
+        final long invalidRequestId = REQUEST_ID_NONE;
+        instance.notifyRequestFailed(invalidRequestId, failureReason);
+        assertFalse(onRequestFailedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+
+        final long validRequestId = requestIds.get(0);
+        instance.notifyRequestFailed(validRequestId, failureReason);
+        assertTrue(onRequestFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
     @Test
     public void testVolumeHandling() throws Exception {
         Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
@@ -429,10 +484,11 @@
             }
 
             @Override
-            public void onControlCategoriesChanged(String packageName,
+            public void onPreferredFeaturesChanged(String packageName,
                     List<String> preferredFeatures) {
                 if (TextUtils.equals(mPackageName, packageName)
-                        && preferredFeatures.equals(routeFeatures)) {
+                        && preferredFeatures.size() == routeFeatures.size()
+                        && preferredFeatures.containsAll(routeFeatures)) {
                     featuresLatch.countDown();
                 }
             }
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
index 3faefdb..9071c85 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/SampleMediaRoute2ProviderService.java
@@ -75,6 +75,7 @@
 
     @GuardedBy("sLock")
     private static SampleMediaRoute2ProviderService sInstance;
+    private Proxy mProxy;
 
     private void initializeRoutes() {
         MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
@@ -179,7 +180,13 @@
     }
 
     @Override
-    public void onSetRouteVolume(String routeId, int volume) {
+    public void onSetRouteVolume(long requestId, String routeId, int volume) {
+        Proxy proxy = mProxy;
+        if (proxy != null) {
+            proxy.onSetRouteVolume(routeId, volume, requestId);
+            return;
+        }
+
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null) {
             return;
@@ -192,7 +199,7 @@
     }
 
     @Override
-    public void onSetSessionVolume(String sessionId, int volume) {
+    public void onSetSessionVolume(long requestId, String sessionId, int volume) {
         RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         if (sessionInfo == null) {
             return;
@@ -205,7 +212,7 @@
     }
 
     @Override
-    public void onCreateSession(String packageName, String routeId, long requestId,
+    public void onCreateSession(long requestId, String packageName, String routeId,
             @Nullable Bundle sessionHints) {
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
@@ -238,7 +245,7 @@
     }
 
     @Override
-    public void onReleaseSession(String sessionId) {
+    public void onReleaseSession(long requestId, String sessionId) {
         RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         if (sessionInfo == null) {
             return;
@@ -258,7 +265,7 @@
     }
 
     @Override
-    public void onSelectRoute(String sessionId, String routeId) {
+    public void onSelectRoute(long requestId, String sessionId, String routeId) {
         RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
         if (route == null || sessionInfo == null) {
@@ -280,7 +287,7 @@
     }
 
     @Override
-    public void onDeselectRoute(String sessionId, String routeId) {
+    public void onDeselectRoute(long requestId, String sessionId, String routeId) {
         RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
 
@@ -308,7 +315,7 @@
     }
 
     @Override
-    public void onTransferToRoute(String sessionId, String routeId) {
+    public void onTransferToRoute(long requestId, String sessionId, String routeId) {
         RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
         MediaRoute2Info route = mRoutes.get(routeId);
 
@@ -347,10 +354,18 @@
         }
 
         String sessionId = mRouteIdToSessionId.get(routeId);
-        onDeselectRoute(sessionId, routeId);
+        onDeselectRoute(REQUEST_ID_NONE, sessionId, routeId);
     }
 
     void publishRoutes() {
         notifyRoutes(mRoutes.values());
     }
+
+    public void setProxy(@Nullable Proxy proxy) {
+        mProxy = proxy;
+    }
+
+    public static class Proxy {
+        public void onSetRouteVolume(String routeId, int volume, long requestId) {}
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 09d4d5f..20b1e0d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -57,6 +57,12 @@
     int PROMPT_REASON_PREPARE_FOR_UPDATE = 6;
 
     /**
+     * Primary auth is required because the user uses weak/convenience biometrics and hasn't used
+     * primary auth since a while
+     */
+    int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
+
+    /**
      * Interface back to keyguard to tell it when security
      * @param callback
      */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9ba3860..52ea300 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -80,6 +80,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -91,6 +92,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -108,6 +110,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.TimeZone;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -264,6 +267,7 @@
     // If the user long pressed the lock icon, disabling face auth for the current session.
     private boolean mLockIconPressed;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private final Executor mBackgroundExecutor;
 
     /**
      * Short delay before restarting biometric authentication after a successful try
@@ -320,12 +324,22 @@
                 }
             };
 
+    private class BiometricAuthenticated {
+        private final boolean mAuthenticated;
+        private final boolean mIsStrongBiometric;
+
+        BiometricAuthenticated(boolean authenticated, boolean isStrongBiometric) {
+            this.mAuthenticated = authenticated;
+            this.mIsStrongBiometric = isStrongBiometric;
+        }
+    }
+
     private SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
     private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
     private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
     private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
-    private SparseBooleanArray mUserFingerprintAuthenticated = new SparseBooleanArray();
-    private SparseBooleanArray mUserFaceAuthenticated = new SparseBooleanArray();
+    private SparseArray<BiometricAuthenticated> mUserFingerprintAuthenticated = new SparseArray<>();
+    private SparseArray<BiometricAuthenticated> mUserFaceAuthenticated = new SparseArray<>();
     private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
     private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
 
@@ -523,10 +537,11 @@
     }
 
     @VisibleForTesting
-    protected void onFingerprintAuthenticated(int userId) {
+    protected void onFingerprintAuthenticated(int userId, boolean isStrongBiometric) {
         Assert.isMainThread();
         Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
-        mUserFingerprintAuthenticated.put(userId, true);
+        mUserFingerprintAuthenticated.put(userId,
+                new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
             mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FINGERPRINT);
@@ -536,7 +551,8 @@
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
-                cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT);
+                cb.onBiometricAuthenticated(userId, BiometricSourceType.FINGERPRINT,
+                        isStrongBiometric);
             }
         }
 
@@ -546,9 +562,21 @@
         // Only authenticate fingerprint once when assistant is visible
         mAssistantVisible = false;
 
+        // Report unlock with strong or non-strong biometric
+        reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+
         Trace.endSection();
     }
 
+    private void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        mBackgroundExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                mLockPatternUtils.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+            }
+        });
+    }
+
     private void handleFingerprintAuthFailed() {
         Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -574,7 +602,7 @@
         }
     }
 
-    private void handleFingerprintAuthenticated(int authUserId) {
+    private void handleFingerprintAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFingerPrintAuthenticated");
         try {
             final int userId;
@@ -592,7 +620,7 @@
                 Log.d(TAG, "Fingerprint disabled by DPM for userId: " + userId);
                 return;
             }
-            onFingerprintAuthenticated(userId);
+            onFingerprintAuthenticated(userId, isStrongBiometric);
         } finally {
             setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
         }
@@ -683,10 +711,11 @@
     }
 
     @VisibleForTesting
-    protected void onFaceAuthenticated(int userId) {
+    protected void onFaceAuthenticated(int userId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
         Assert.isMainThread();
-        mUserFaceAuthenticated.put(userId, true);
+        mUserFaceAuthenticated.put(userId,
+                new BiometricAuthenticated(true, isStrongBiometric));
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
             mTrustManager.unlockedByBiometricForUser(userId, BiometricSourceType.FACE);
@@ -697,7 +726,8 @@
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
                 cb.onBiometricAuthenticated(userId,
-                        BiometricSourceType.FACE);
+                        BiometricSourceType.FACE,
+                        isStrongBiometric);
             }
         }
 
@@ -707,6 +737,9 @@
         // Only authenticate face once when assistant is visible
         mAssistantVisible = false;
 
+        // Report unlock with strong or non-strong biometric
+        reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+
         Trace.endSection();
     }
 
@@ -737,7 +770,7 @@
         }
     }
 
-    private void handleFaceAuthenticated(int authUserId) {
+    private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
         Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
         try {
             if (mGoingToSleep) {
@@ -760,7 +793,7 @@
                 return;
             }
             if (DEBUG_FACE) Log.d(TAG, "Face auth succeeded for user " + userId);
-            onFaceAuthenticated(userId);
+            onFaceAuthenticated(userId, isStrongBiometric);
         } finally {
             setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         }
@@ -914,9 +947,13 @@
      * Returns whether the user is unlocked with biometrics.
      */
     public boolean getUserUnlockedWithBiometric(int userId) {
-        boolean fingerprintOrFace = mUserFingerprintAuthenticated.get(userId)
-                || mUserFaceAuthenticated.get(userId);
-        return fingerprintOrFace && isUnlockingWithBiometricAllowed();
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
+        boolean fingerprintAllowed = fingerprint != null && fingerprint.mAuthenticated
+                && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric);
+        boolean faceAllowed = face != null && face.mAuthenticated
+                && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric);
+        return fingerprintAllowed || faceAllowed;
     }
 
     public boolean getUserTrustIsManaged(int userId) {
@@ -970,8 +1007,8 @@
         return mUserTrustIsUsuallyManaged.get(userId);
     }
 
-    public boolean isUnlockingWithBiometricAllowed() {
-        return mStrongAuthTracker.isUnlockingWithBiometricAllowed();
+    public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
+        return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric);
     }
 
     public boolean isUserInLockdown(int userId) {
@@ -1169,7 +1206,7 @@
         @Override
         public void onAuthenticationSucceeded(AuthenticationResult result) {
             Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
-            handleFingerprintAuthenticated(result.getUserId());
+            handleFingerprintAuthenticated(result.getUserId(), result.isStrongBiometric());
             Trace.endSection();
         }
 
@@ -1201,7 +1238,7 @@
         @Override
         public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
             Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
-            handleFaceAuthenticated(result.getUserId());
+            handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
             Trace.endSection();
         }
 
@@ -1305,9 +1342,9 @@
             mStrongAuthRequiredChangedCallback = strongAuthRequiredChangedCallback;
         }
 
-        public boolean isUnlockingWithBiometricAllowed() {
+        public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
             int userId = getCurrentUser();
-            return isBiometricAllowedForUser(userId);
+            return isBiometricAllowedForUser(isStrongBiometric, userId);
         }
 
         public boolean hasUserAuthenticatedSinceBoot() {
@@ -1438,12 +1475,14 @@
             Context context,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
-            DumpController dumpController) {
+            DumpController dumpController,
+            @Background Executor backgroundExecutor) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
         mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
         dumpController.registerDumpable(this);
+        mBackgroundExecutor = backgroundExecutor;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -1753,14 +1792,16 @@
     }
 
     private boolean shouldListenForFingerprintAssistant() {
+        BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(getCurrentUser());
         return mAssistantVisible && mKeyguardOccluded
-                && !mUserFingerprintAuthenticated.get(getCurrentUser(), false)
+                && !(fingerprint != null && fingerprint.mAuthenticated)
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
 
     private boolean shouldListenForFaceAssistant() {
+        BiometricAuthenticated face = mUserFaceAuthenticated.get(getCurrentUser());
         return mAssistantVisible && mKeyguardOccluded
-                && !mUserFaceAuthenticated.get(getCurrentUser(), false)
+                && !(face != null && face.mAuthenticated)
                 && !mUserHasTrust.get(getCurrentUser(), false);
     }
 
@@ -1817,7 +1858,7 @@
     public void onLockIconPressed() {
         mLockIconPressed = true;
         final int userId = getCurrentUser();
-        mUserFaceAuthenticated.put(userId, false);
+        mUserFaceAuthenticated.put(userId, null);
         updateFaceListeningState();
         mStrongAuthTracker.onStrongAuthRequiredChanged(userId);
     }
@@ -2691,9 +2732,11 @@
         if (mFpm != null && mFpm.isHardwareDetected()) {
             final int userId = ActivityManager.getCurrentUser();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+            BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
             pw.println("  Fingerprint state (user=" + userId + ")");
-            pw.println("    allowed=" + isUnlockingWithBiometricAllowed());
-            pw.println("    auth'd=" + mUserFingerprintAuthenticated.get(userId));
+            pw.println("    allowed=" + fingerprint != null
+                    && isUnlockingWithBiometricAllowed(fingerprint.mIsStrongBiometric));
+            pw.println("    auth'd=" + fingerprint != null && fingerprint.mAuthenticated);
             pw.println("    authSinceBoot="
                     + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
             pw.println("    disabled(DPM)=" + isFingerprintDisabled(userId));
@@ -2706,9 +2749,11 @@
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
             final int userId = ActivityManager.getCurrentUser();
             final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
+            BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
             pw.println("  Face authentication state (user=" + userId + ")");
-            pw.println("    allowed=" + isUnlockingWithBiometricAllowed());
-            pw.println("    auth'd=" + mUserFaceAuthenticated.get(userId));
+            pw.println("    allowed=" + face != null
+                    && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric));
+            pw.println("    auth'd=" + face != null && face.mAuthenticated);
             pw.println("    authSinceBoot="
                     + getStrongAuthTracker().hasUserAuthenticatedSinceBoot());
             pw.println("    disabled(DPM)=" + isFaceDisabled(userId));
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 49f72a9..12e0ecd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -239,7 +239,8 @@
      * @param userId the user id for which the biometric sample was authenticated
      * @param biometricSourceType
      */
-    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) { }
+    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) { }
 
     /**
      * Called when biometric authentication provides help string (e.g. "Try again")
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 1a47dac..dc0cb03 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -96,6 +96,7 @@
     private void fakeWakeAndUnlock() {
         mBiometricUnlockController.onBiometricAcquired(BiometricSourceType.FINGERPRINT);
         mBiometricUnlockController.onBiometricAuthenticated(
-                KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT);
+                KeyguardUpdateMonitor.getCurrentUser(), BiometricSourceType.FINGERPRINT,
+                true /* isStrongBiometric */);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index 6a64c83..f719cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -130,7 +130,8 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
+                        BiometricSourceType biometricSourceType,
+                        boolean isStrongBiometric) {
                     if (userId == KeyguardUpdateMonitor.getCurrentUser()
                             && biometricSourceType == BiometricSourceType.FACE) {
                         mJustUnlockedWithFace = true;
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
index 2f3e336..a084ae6 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java
@@ -78,7 +78,8 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
+                        BiometricSourceType biometricSourceType,
+                        boolean isStrongBiometric) {
                     if (userId == KeyguardUpdateMonitor.getCurrentUser()
                             && biometricSourceType == BiometricSourceType.FACE) {
                         mJustUnlockedWithFace = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 374153c..2e6c955 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -538,7 +539,8 @@
         }
 
         @Override
-        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
             if (mLockPatternUtils.isSecure(userId)) {
                 mLockPatternUtils.getDevicePolicyManager().reportSuccessfulBiometricAttempt(
                         userId);
@@ -675,6 +677,9 @@
                 return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
             } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
+            } else if (any && (strongAuth
+                    & STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT) != 0) {
+                return KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT;
             }
             return KeyguardSecurityView.PROMPT_REASON_NONE;
         }
@@ -1842,6 +1847,13 @@
             mShowKeyguardWakeLock.release();
         }
         mKeyguardDisplayManager.show();
+
+        // schedule 4hr idle timeout after which non-strong biometrics (i.e. weak or convenience
+        // biometric) can't be used to unlock device until unlocking with strong biometric or
+        // primary auth (i.e. PIN/pattern/password)
+        mLockPatternUtils.scheduleNonStrongBiometricIdleTimeout(
+                KeyguardUpdateMonitor.getCurrentUser());
+
         Trace.endSection();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9e1e347..f06cd54 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -497,9 +497,9 @@
         flashOutAnimator.addUpdateListener(animation ->
                 mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
 
-        final PointF startPos = new PointF((float) bounds.left, (float) bounds.top);
-        final PointF finalPos = new PointF(mScreenshotOffsetXPx,
-                mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale);
+        final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
+        final PointF finalPos = new PointF(mScreenshotOffsetXPx + width * cornerScale / 2f,
+                mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f);
 
         ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
         toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
@@ -517,11 +517,13 @@
             }
 
             if (t < xPositionPct) {
-                mScreenshotView.setX(MathUtils.lerp(
-                        startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)));
+                float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
+                        mFastOutSlowIn.getInterpolation(t / xPositionPct));
+                mScreenshotView.setX(xCenter - width * mScreenshotView.getScaleX() / 2f);
             }
-            mScreenshotView.setY(MathUtils.lerp(
-                    startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)));
+            float yCenter = MathUtils.lerp(startPos.y, finalPos.y,
+                    mFastOutSlowIn.getInterpolation(t));
+            mScreenshotView.setY(yCenter - height * mScreenshotView.getScaleY() / 2f);
         });
 
         toCorner.addListener(new AnimatorListenerAdapter() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 7de70f5..2571521 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -281,7 +281,8 @@
                         .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                         .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
                                 mSmartActionsEnabled)
-                        .setAction(Intent.ACTION_SEND),
+                        .setAction(Intent.ACTION_SEND)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
 
         Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
@@ -310,7 +311,8 @@
                         .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                         .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
                                 mSmartActionsEnabled)
-                        .setAction(Intent.ACTION_EDIT),
+                        .setAction(Intent.ACTION_EDIT)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                 PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
         Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
                 Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
@@ -324,7 +326,8 @@
                             .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
                             .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
                             .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
-                                    mSmartActionsEnabled),
+                                    mSmartActionsEnabled)
+                            .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
                     PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
             Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
                     Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
@@ -361,9 +364,9 @@
             String actionType = extras.getString(
                     ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
                     ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
-            Intent intent = new Intent(context,
-                    GlobalScreenshot.SmartActionsReceiver.class).putExtra(
-                    GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
+            Intent intent = new Intent(context, GlobalScreenshot.SmartActionsReceiver.class)
+                    .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent)
+                    .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
             PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
                     mRandom.nextInt(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index ab69d47..4bb8621 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -90,7 +90,7 @@
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
-        IndentingPrintWriter(pw, "  ").use {
+        IndentingPrintWriter(pw, "  ").let {
             it.println("BlurUtils:")
             it.increaseIndent()
             it.println("minBlurRadius: $minBlurRadius")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 7d3d406..4f8e6cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -645,7 +645,13 @@
         @Override
         public void onBiometricHelp(int msgId, String helpString,
                 BiometricSourceType biometricSourceType) {
-            if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+            // TODO(b/141025588): refactor to reduce repetition of code/comments
+            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+            // check of whether non-strong biometric is allowed
+            if (!mKeyguardUpdateMonitor
+                    .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
                 return;
             }
             boolean showSwipeToUnlock =
@@ -705,13 +711,21 @@
 
         private boolean shouldSuppressFingerprintError(int msgId,
                 KeyguardUpdateMonitor updateMonitor) {
-            return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+            // check of whether non-strong biometric is allowed
+            return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
                     && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
         }
 
         private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
-            return ((!updateMonitor.isUnlockingWithBiometricAllowed()
+            // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+            // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+            // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+            // check of whether non-strong biometric is allowed
+            return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
                     && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
                     || msgId == FaceManager.FACE_ERROR_CANCELED);
         }
@@ -745,8 +759,9 @@
         }
 
         @Override
-        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
-            super.onBiometricAuthenticated(userId, biometricSourceType);
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
+            super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
             mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 691e1c4..1dde5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -150,14 +150,26 @@
     private KeyguardViewMediator mKeyguardViewMediator;
     private ScrimController mScrimController;
     private StatusBar mStatusBar;
-    private int mPendingAuthenticatedUserId = -1;
-    private BiometricSourceType mPendingAuthenticatedBioSourceType = null;
+    private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mPendingShowBouncer;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
 
     private final MetricsLogger mMetricsLogger;
 
+    private static final class PendingAuthenticated {
+        public final int userId;
+        public final BiometricSourceType biometricSourceType;
+        public final boolean isStrongBiometric;
+
+        PendingAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
+            this.userId = userId;
+            this.biometricSourceType = biometricSourceType;
+            this.isStrongBiometric = isStrongBiometric;
+        }
+    }
+
     @Inject
     public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
             KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
@@ -251,28 +263,30 @@
     }
 
     @Override
-    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+    public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
         Trace.beginSection("BiometricUnlockController#onBiometricAuthenticated");
         if (mUpdateMonitor.isGoingToSleep()) {
-            mPendingAuthenticatedUserId = userId;
-            mPendingAuthenticatedBioSourceType = biometricSourceType;
+            mPendingAuthenticated = new PendingAuthenticated(userId, biometricSourceType,
+                    isStrongBiometric);
             Trace.endSection();
             return;
         }
         mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
                 .setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
         boolean unlockAllowed = mKeyguardBypassController.onBiometricAuthenticated(
-                biometricSourceType);
+                biometricSourceType, isStrongBiometric);
         if (unlockAllowed) {
             mKeyguardViewMediator.userActivity();
-            startWakeAndUnlock(biometricSourceType);
+            startWakeAndUnlock(biometricSourceType, isStrongBiometric);
         } else {
             Log.d(TAG, "onBiometricAuthenticated aborted by bypass controller");
         }
     }
 
-    public void startWakeAndUnlock(BiometricSourceType biometricSourceType) {
-        startWakeAndUnlock(calculateMode(biometricSourceType));
+    public void startWakeAndUnlock(BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
+        startWakeAndUnlock(calculateMode(biometricSourceType, isStrongBiometric));
     }
 
     public void startWakeAndUnlock(@WakeAndUnlockMode int mode) {
@@ -373,45 +387,46 @@
     public void onStartedGoingToSleep(int why) {
         resetMode();
         mFadedAwayAfterWakeAndUnlock = false;
-        mPendingAuthenticatedUserId = -1;
-        mPendingAuthenticatedBioSourceType = null;
+        mPendingAuthenticated = null;
     }
 
     @Override
     public void onFinishedGoingToSleep(int why) {
         Trace.beginSection("BiometricUnlockController#onFinishedGoingToSleep");
-        BiometricSourceType pendingType = mPendingAuthenticatedBioSourceType;
-        int pendingUserId = mPendingAuthenticatedUserId;
-        if (pendingUserId != -1 && pendingType != null) {
+        if (mPendingAuthenticated != null) {
             // Post this to make sure it's executed after the device is fully locked.
-            mHandler.post(() -> onBiometricAuthenticated(pendingUserId, pendingType));
+            mHandler.post(() -> onBiometricAuthenticated(mPendingAuthenticated.userId,
+                    mPendingAuthenticated.biometricSourceType,
+                    mPendingAuthenticated.isStrongBiometric));
+            mPendingAuthenticated = null;
         }
-        mPendingAuthenticatedUserId = -1;
-        mPendingAuthenticatedBioSourceType = null;
         Trace.endSection();
     }
 
     public boolean hasPendingAuthentication() {
-        return mPendingAuthenticatedUserId != -1
-                && mUpdateMonitor.isUnlockingWithBiometricAllowed()
-                && mPendingAuthenticatedUserId == KeyguardUpdateMonitor.getCurrentUser();
+        return mPendingAuthenticated != null
+                && mUpdateMonitor
+                    .isUnlockingWithBiometricAllowed(mPendingAuthenticated.isStrongBiometric)
+                && mPendingAuthenticated.userId == KeyguardUpdateMonitor.getCurrentUser();
     }
 
     public int getMode() {
         return mMode;
     }
 
-    private @WakeAndUnlockMode int calculateMode(BiometricSourceType biometricSourceType) {
+    private @WakeAndUnlockMode int calculateMode(BiometricSourceType biometricSourceType,
+            boolean isStrongBiometric) {
         if (biometricSourceType == BiometricSourceType.FACE
                 || biometricSourceType == BiometricSourceType.IRIS) {
-            return calculateModeForPassiveAuth();
+            return calculateModeForPassiveAuth(isStrongBiometric);
         } else {
-            return calculateModeForFingerprint();
+            return calculateModeForFingerprint(isStrongBiometric);
         }
     }
 
-    private @WakeAndUnlockMode int calculateModeForFingerprint() {
-        boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
+    private @WakeAndUnlockMode int calculateModeForFingerprint(boolean isStrongBiometric) {
+        boolean unlockingAllowed =
+                mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
 
         if (!mUpdateMonitor.isDeviceInteractive()) {
@@ -440,8 +455,9 @@
         return MODE_NONE;
     }
 
-    private @WakeAndUnlockMode int calculateModeForPassiveAuth() {
-        boolean unlockingAllowed = mUpdateMonitor.isUnlockingWithBiometricAllowed();
+    private @WakeAndUnlockMode int calculateModeForPassiveAuth(boolean isStrongBiometric) {
+        boolean unlockingAllowed =
+                mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric);
         boolean deviceDreaming = mUpdateMonitor.isDreaming();
         boolean bypass = mKeyguardBypassController.getBypassEnabled();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index b4d0d47..03918e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -38,11 +38,20 @@
     private val mKeyguardStateController: KeyguardStateController
     private val statusBarStateController: StatusBarStateController
     private var hasFaceFeature: Boolean
+    private var pendingUnlock: PendingUnlock? = null
 
     /**
+     * Pending unlock info:
+     *
      * The pending unlock type which is set if the bypass was blocked when it happened.
+     *
+     * Whether the pending unlock type is strong biometric or non-strong biometric
+     * (i.e. weak or convenience).
      */
-    private var pendingUnlockType: BiometricSourceType? = null
+    private data class PendingUnlock(
+        val pendingUnlockType: BiometricSourceType,
+        val isStrongBiometric: Boolean
+    )
 
     lateinit var unlockController: BiometricUnlockController
     var isPulseExpanding = false
@@ -86,7 +95,7 @@
         statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
             override fun onStateChanged(newState: Int) {
                 if (newState != StatusBarState.KEYGUARD) {
-                    pendingUnlockType = null
+                    pendingUnlock = null
                 }
             }
         })
@@ -101,7 +110,7 @@
         lockscreenUserManager.addUserChangedListener(
                 object : NotificationLockscreenUserManager.UserChangedListener {
                     override fun onUserChanged(userId: Int) {
-                        pendingUnlockType = null
+                        pendingUnlock = null
                     }
                 })
     }
@@ -111,11 +120,14 @@
      *
      * @return false if we can not wake and unlock right now
      */
-    fun onBiometricAuthenticated(biometricSourceType: BiometricSourceType): Boolean {
+    fun onBiometricAuthenticated(
+        biometricSourceType: BiometricSourceType,
+        isStrongBiometric: Boolean
+    ): Boolean {
         if (bypassEnabled) {
             val can = canBypass()
             if (!can && (isPulseExpanding || qSExpanded)) {
-                pendingUnlockType = biometricSourceType
+                pendingUnlock = PendingUnlock(biometricSourceType, isStrongBiometric)
             }
             return can
         }
@@ -123,10 +135,12 @@
     }
 
     fun maybePerformPendingUnlock() {
-        if (pendingUnlockType != null) {
-            if (onBiometricAuthenticated(pendingUnlockType!!)) {
-                unlockController.startWakeAndUnlock(pendingUnlockType)
-                pendingUnlockType = null
+        if (pendingUnlock != null) {
+            if (onBiometricAuthenticated(pendingUnlock!!.pendingUnlockType,
+                            pendingUnlock!!.isStrongBiometric)) {
+                unlockController.startWakeAndUnlock(pendingUnlock!!.pendingUnlockType,
+                        pendingUnlock!!.isStrongBiometric)
+                pendingUnlock = null
             }
         }
     }
@@ -162,12 +176,17 @@
     }
 
     fun onStartedGoingToSleep() {
-        pendingUnlockType = null
+        pendingUnlock = null
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
         pw.println("KeyguardBypassController:")
-        pw.println("  pendingUnlockType: $pendingUnlockType")
+        if (pendingUnlock != null) {
+            pw.println("  mPendingUnlock.pendingUnlockType: ${pendingUnlock!!.pendingUnlockType}")
+            pw.println("  mPendingUnlock.isStrongBiometric: ${pendingUnlock!!.isStrongBiometric}")
+        } else {
+            pw.println("  mPendingUnlock: $pendingUnlock")
+        }
         pw.println("  bypassEnabled: $bypassEnabled")
         pw.println("  canBypass: ${canBypass()}")
         pw.println("  bouncerShowing: $bouncerShowing")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 60589843..61cef68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -387,7 +387,12 @@
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         boolean fingerprintRunning = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
-        boolean unlockingAllowed = mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed();
+        // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+        // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
+        // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+        // check of whether non-strong biometric is allowed
+        boolean unlockingAllowed = mKeyguardUpdateMonitor
+                        .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
         if (fingerprintRunning && unlockingAllowed) {
             AccessibilityNodeInfo.AccessibilityAction unlock
                     = new AccessibilityNodeInfo.AccessibilityAction(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index fb7976f..c61d7bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -202,8 +202,10 @@
 
                 @Override
                 public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
-                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+                        BiometricSourceType biometricSourceType,
+                        boolean isStrongBiometric) {
+                    if (mFirstBypassAttempt
+                            && mUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
                         mDelayShowingKeyguardStatusBar = true;
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 0ab08a8..a7f60d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -293,13 +293,12 @@
         }
 
         @Override
-        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
+        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
+                boolean isStrongBiometric) {
             Trace.beginSection("KeyguardUpdateMonitorCallback#onBiometricAuthenticated");
-            if (!mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()) {
-                Trace.endSection();
-                return;
+            if (mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(isStrongBiometric)) {
+                update(false /* updateAlways */);
             }
-            update(false /* updateAlways */);
             Trace.endSection();
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index befe3e1..6a093963 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -22,6 +22,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -75,6 +76,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
@@ -117,6 +119,8 @@
     private SubscriptionManager mSubscriptionManager;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private Executor mBackgroundExecutor;
     private TestableLooper mTestableLooper;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
@@ -137,7 +141,9 @@
         when(mFaceManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
         when(mUserManager.isPrimaryUser()).thenReturn(true);
-        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mStrongAuthTracker
+                .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
+                .thenReturn(true);
         context.addMockSystemService(TrustManager.class, mTrustManager);
         context.addMockSystemService(FingerprintManager.class, mFingerprintManager);
         context.addMockSystemService(BiometricManager.class, mBiometricManager);
@@ -450,7 +456,10 @@
 
     @Test
     public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
-        mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser());
+        // test whether face will be skipped if authenticated, so the value of isStrongBiometric
+        // doesn't matter here
+        mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
+                true /* isStrongBiometric */);
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
         mTestableLooper.processAllMessages();
 
@@ -460,18 +469,36 @@
     @Test
     public void testGetUserCanSkipBouncer_whenFace() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFaceAuthenticated(user);
+        mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
     @Test
+    public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+                .thenReturn(false);
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */);
+        assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
+    }
+
+    @Test
     public void testGetUserCanSkipBouncer_whenFingerprint() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
-        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user);
+        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */);
         assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
     }
 
     @Test
+    public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+                .thenReturn(false);
+        int user = KeyguardUpdateMonitor.getCurrentUser();
+        mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */);
+        assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
+    }
+
+    @Test
     public void testGetUserCanSkipBouncer_whenTrust() {
         int user = KeyguardUpdateMonitor.getCurrentUser();
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */, user, 0 /* flags */);
@@ -585,7 +612,7 @@
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
-                    mBroadcastDispatcher, mDumpController);
+                    mBroadcastDispatcher, mDumpController, mBackgroundExecutor);
             mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 1d4b4be..581d795 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
@@ -124,7 +125,7 @@
         mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
         mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
 
-        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
         when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
         when(mIndicationArea.findViewById(R.id.keyguard_indication_text)).thenReturn(mTextView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 769b774..813923d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -97,7 +97,8 @@
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
         when(mKeyguardStateController.isFaceAuthEnabled()).thenReturn(true);
-        when(mKeyguardBypassController.onBiometricAuthenticated(any())).thenReturn(true);
+        when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
+                .thenReturn(true);
         when(mKeyguardBypassController.canPlaySubtleWindowAnimations()).thenReturn(true);
         mContext.addMockSystemService(PowerManager.class, mPowerManager);
         mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
@@ -112,11 +113,28 @@
 
     @Test
     public void onBiometricAuthenticated_whenFingerprintAndBiometricsDisallowed_showBouncer() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */))
+                .thenReturn(false);
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
         verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
         verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
                 anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
+    }
+
+    @Test
+    public void onBiometricAuthenticated_whenFingerprint_nonStrongBioDisallowed_showBouncer() {
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+                .thenReturn(false);
+        mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+                BiometricSourceType.FINGERPRINT, false /* isStrongBiometric */);
+        verify(mStatusBarKeyguardViewManager).showBouncer(eq(false));
+        verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
+                anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
     }
 
     @Test
@@ -124,44 +142,60 @@
         reset(mUpdateMonitor);
         reset(mStatusBarKeyguardViewManager);
         when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mDozeScrimController.isPulsing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
         verify(mKeyguardViewMediator).onWakeAndUnlocking();
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING);
     }
 
     @Test
     public void onBiometricAuthenticated_whenFingerprint_dismissKeyguard() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
         verify(mShadeController).animateCollapsePanels(anyInt(), anyBoolean(), anyBoolean(),
                 anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
     }
 
     @Test
     public void onBiometricAuthenticated_whenFingerprintOnBouncer_dismissBouncer() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FINGERPRINT);
+                BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_DISMISS_BOUNCER);
     }
 
     @Test
     public void onBiometricAuthenticated_whenFace_dontDismissKeyguard() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
                 anyBoolean(), anyFloat());
         verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_NONE);
     }
 
     @Test
@@ -169,13 +203,17 @@
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
 
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
                 anyBoolean(), anyFloat());
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_UNLOCK_FADING);
     }
 
     @Test
@@ -184,9 +222,11 @@
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
 
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(false);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         // Wake up before showing the bouncer
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(eq(false));
@@ -202,9 +242,11 @@
         reset(mUpdateMonitor);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
 
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(false);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
@@ -215,23 +257,30 @@
 
     @Test
     public void onBiometricAuthenticated_whenFaceOnBouncer_dismissBouncer() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_DISMISS_BOUNCER);
     }
 
     @Test
     public void onBiometricAuthenticated_whenBypassOnBouncer_dismissBouncer() {
         reset(mKeyguardBypassController);
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        when(mKeyguardBypassController.onBiometricAuthenticated(any())).thenReturn(true);
+        when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
+                .thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
         assertThat(mBiometricUnlockController.getMode())
@@ -240,11 +289,13 @@
 
     @Test
     public void onBiometricAuthenticated_whenBypassOnBouncer_respectsCanPlaySubtleAnim() {
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
         when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
         assertThat(mBiometricUnlockController.getMode())
@@ -255,13 +306,17 @@
     public void onBiometricAuthenticated_whenFaceAndPulsing_dontDismissKeyguard() {
         reset(mUpdateMonitor);
         reset(mStatusBarKeyguardViewManager);
-        when(mUpdateMonitor.isUnlockingWithBiometricAllowed()).thenReturn(true);
+        when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
         when(mDozeScrimController.isPulsing()).thenReturn(true);
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
 
         verify(mShadeController, never()).animateCollapsePanels(anyInt(), anyBoolean(),
                 anyBoolean(), anyFloat());
+        assertThat(mBiometricUnlockController.getMode())
+                .isEqualTo(BiometricUnlockController.MODE_ONLY_WAKE);
     }
 
     @Test
@@ -270,8 +325,10 @@
         mBiometricUnlockController.onFinishedGoingToSleep(-1);
         verify(mHandler, never()).post(any());
 
+        // the value of isStrongBiometric doesn't matter here since we only care about the returned
+        // value of isUnlockingWithBiometricAllowed()
         mBiometricUnlockController.onBiometricAuthenticated(1 /* userId */,
-                BiometricSourceType.FACE);
+                BiometricSourceType.FACE, true /* isStrongBiometric */);
         mBiometricUnlockController.onFinishedGoingToSleep(-1);
         verify(mHandler).post(any());
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f544517..317ce4c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1158,6 +1158,18 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error requesting to hide fill UI", e);
             }
+            try {
+                final InlineSuggestionSession.ImeResponse imeResponse =
+                        mInlineSuggestionSession.waitAndGetImeResponse();
+                if (imeResponse == null) {
+                    Log.w(TAG, "Session input method callback is not set yet");
+                    return;
+                }
+                imeResponse.getCallback().onInlineSuggestionsResponse(
+                        new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+            } catch (RemoteException e) {
+                Slog.e(TAG, "RemoteException hiding inline suggestions");
+            }
         }
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index a8886fc..17cdf61 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -82,12 +82,17 @@
         if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
         final BiConsumer<Dataset, Integer> onClickFactory;
         if (response.getAuthentication() != null) {
-            onClickFactory = (dataset, datasetIndex) -> client.authenticate(response.getRequestId(),
-                    datasetIndex, response.getAuthentication(), response.getClientState(),
-                    /* authenticateInline= */ true);
+            onClickFactory = (dataset, datasetIndex) -> {
+                client.requestHideFillUi(autofillId);
+                client.authenticate(response.getRequestId(),
+                        datasetIndex, response.getAuthentication(), response.getClientState(),
+                        /* authenticateInline= */ true);
+            };
         } else {
-            onClickFactory = (dataset, datasetIndex) ->
-                    client.fill(response.getRequestId(), datasetIndex, dataset);
+            onClickFactory = (dataset, datasetIndex) -> {
+                client.requestHideFillUi(autofillId);
+                client.fill(response.getRequestId(), datasetIndex, dataset);
+            };
         }
 
         final List<Dataset> datasetList = response.getDatasets();
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 5d8a0f6..4670d58 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -401,10 +401,10 @@
             pw.print(prefix); pw.print("hasStartedWhitelistingBgActivityStarts=");
             pw.println(mHasStartedWhitelistingBgActivityStarts);
         }
-        if (mAllowWhileInUsePermissionInFgs) {
-            pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
-            pw.println(mAllowWhileInUsePermissionInFgs);
-        }
+        pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
+                pw.println(mAllowWhileInUsePermissionInFgs);
+        pw.print(prefix); pw.print("recentCallingPackage=");
+                pw.println(mRecentCallingPackage);
         if (delayed) {
             pw.print(prefix); pw.print("delayed="); pw.println(delayed);
         }
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 204f072..8ed221d 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -311,6 +311,7 @@
                 }
 
                 authenticator = new FingerprintAuthenticator(fingerprintService);
+                fingerprintService.initConfiguredStrength(config.mStrength);
                 break;
 
             case TYPE_FACE:
@@ -322,6 +323,7 @@
                 }
 
                 authenticator = new FaceAuthenticator(faceService);
+                faceService.initConfiguredStrength(config.mStrength);
                 break;
 
             case TYPE_IRIS:
@@ -333,6 +335,7 @@
                 }
 
                 authenticator = new IrisAuthenticator(irisService);
+                irisService.initConfiguredStrength(config.mStrength);
                 break;
 
             default:
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 0e70994..74c70df 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -31,6 +31,7 @@
 import android.content.pm.UserInfo;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricNativeHandle;
 import android.hardware.biometrics.IBiometricService;
@@ -106,6 +107,7 @@
     private PerformanceStats mPerformanceStats;
     protected int mCurrentUserId = UserHandle.USER_NULL;
     protected long mHalDeviceId;
+    private int mOEMStrength; // Tracks the OEM configured biometric modality strength
     // Tracks if the current authentication makes use of CryptoObjects.
     protected boolean mIsCrypto;
     // Normal authentications are tracked by mPerformanceMap.
@@ -681,6 +683,20 @@
                 statsModality(), BiometricsProtoEnums.ISSUE_HAL_DEATH);
     }
 
+    protected void initConfiguredStrengthInternal(int strength) {
+        if (DEBUG) {
+            Slog.d(getTag(), "initConfiguredStrengthInternal(" + strength + ")");
+        }
+        mOEMStrength = strength;
+    }
+
+    protected boolean isStrongBiometric() {
+        // TODO(b/141025588): need to calculate actual strength when downgrading tiers
+        final int biometricBits = mOEMStrength
+                & BiometricManager.Authenticators.BIOMETRIC_MIN_STRENGTH;
+        return biometricBits == BiometricManager.Authenticators.BIOMETRIC_STRONG;
+    }
+
     protected ClientMonitor getCurrentClient() {
         return mCurrentClient;
     }
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 31c3d4d..a87a455 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -740,6 +740,12 @@
             }
             return 0;
         }
+
+        @Override // Binder call
+        public void initConfiguredStrength(int strength) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            initConfiguredStrengthInternal(strength);
+        }
     }
 
     /**
@@ -809,7 +815,7 @@
             if (mFaceServiceReceiver != null) {
                 if (biometric == null || biometric instanceof Face) {
                     mFaceServiceReceiver.onAuthenticationSucceeded(deviceId, (Face) biometric,
-                            userId);
+                            userId, isStrongBiometric());
                 } else {
                     Slog.e(TAG, "onAuthenticationSucceeded received non-face biometric");
                 }
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 0a61988..83aa9cf5 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.MANAGE_FINGERPRINT;
 import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 import static android.Manifest.permission.USE_BIOMETRIC;
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.Manifest.permission.USE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 
@@ -462,6 +463,12 @@
             checkPermission(MANAGE_FINGERPRINT);
             mClientActiveCallbacks.remove(callback);
         }
+
+        @Override // Binder call
+        public void initConfiguredStrength(int strength) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            initConfiguredStrengthInternal(strength);
+        }
     }
 
     /**
@@ -526,8 +533,8 @@
                 throws RemoteException {
             if (mFingerprintServiceReceiver != null) {
                 if (biometric == null || biometric instanceof Fingerprint) {
-                    mFingerprintServiceReceiver
-                            .onAuthenticationSucceeded(deviceId, (Fingerprint) biometric, userId);
+                    mFingerprintServiceReceiver.onAuthenticationSucceeded(deviceId,
+                            (Fingerprint) biometric, userId, isStrongBiometric());
                 } else {
                     Slog.e(TAG, "onAuthenticationSucceeded received non-fingerprint biometric");
                 }
diff --git a/services/core/java/com/android/server/biometrics/iris/IrisService.java b/services/core/java/com/android/server/biometrics/iris/IrisService.java
index 2817315..903ae6b 100644
--- a/services/core/java/com/android/server/biometrics/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/iris/IrisService.java
@@ -16,9 +16,12 @@
 
 package com.android.server.biometrics.iris;
 
+import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.iris.IIrisService;
 
 import com.android.server.biometrics.AuthenticationClient;
 import com.android.server.biometrics.BiometricServiceBase;
@@ -42,6 +45,17 @@
     private static final String TAG = "IrisService";
 
     /**
+     * Receives the incoming binder calls from IrisManager.
+     */
+    private final class IrisServiceWrapper extends IIrisService.Stub {
+        @Override // Binder call
+        public void initConfiguredStrength(int strength) {
+            checkPermission(USE_BIOMETRIC_INTERNAL);
+            initConfiguredStrengthInternal(strength);
+        }
+    }
+
+    /**
      * Initializes the system service.
      * <p>
      * Subclasses must define a single argument constructor that accepts the context
@@ -57,6 +71,7 @@
     @Override
     public void onStart() {
         super.onStart();
+        publishBinderService(Context.IRIS_SERVICE, new IrisServiceWrapper());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1f4048f..15dfab9 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings;
 
 import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
+import static android.Manifest.permission.MANAGE_BIOMETRIC;
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -178,6 +179,7 @@
 public class LockSettingsService extends ILockSettings.Stub {
     private static final String TAG = "LockSettingsService";
     private static final String PERMISSION = ACCESS_KEYGUARD_SECURE_STORAGE;
+    private static final String BIOMETRIC_PERMISSION = MANAGE_BIOMETRIC;
     private static final boolean DEBUG = false;
 
     private static final int PROFILE_KEY_IV_SIZE = 12;
@@ -1050,6 +1052,10 @@
         }
     }
 
+    private final void checkBiometricPermission() {
+        mContext.enforceCallingOrSelfPermission(BIOMETRIC_PERMISSION, "LockSettingsBiometric");
+    }
+
     @Override
     public boolean hasSecureLockScreen() {
         return mHasSecureLockScreen;
@@ -2304,6 +2310,18 @@
     }
 
     @Override
+    public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        checkBiometricPermission();
+        mStrongAuth.reportSuccessfulBiometricUnlock(isStrongBiometric, userId);
+    }
+
+    @Override
+    public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+        checkBiometricPermission();
+        mStrongAuth.scheduleNonStrongBiometricIdleTimeout(userId);
+    }
+
+    @Override
     public void userPresent(int userId) {
         checkWritePermission(userId);
         mStrongAuth.reportUnlock(userId);
@@ -3191,6 +3209,12 @@
         mStorage.dump(pw);
         pw.println();
         pw.decreaseIndent();
+
+        pw.println("StrongAuth:");
+        pw.increaseIndent();
+        mStrongAuth.dump(pw);
+        pw.println();
+        pw.decreaseIndent();
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index 91cf53e..fbee6f4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -17,6 +17,7 @@
 package com.android.server.locksettings;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 
 import android.app.AlarmManager;
@@ -32,8 +33,10 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
 
 /**
@@ -42,6 +45,7 @@
 public class LockSettingsStrongAuth {
 
     private static final String TAG = "LockSettings";
+    private static final boolean DEBUG = false;
 
     private static final int MSG_REQUIRE_STRONG_AUTH = 1;
     private static final int MSG_REGISTER_TRACKER = 2;
@@ -49,15 +53,40 @@
     private static final int MSG_REMOVE_USER = 4;
     private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
     private static final int MSG_NO_LONGER_REQUIRE_STRONG_AUTH = 6;
+    private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
+    private static final int MSG_STRONG_BIOMETRIC_UNLOCK = 8;
+    private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT = 9;
 
     private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
             "LockSettingsStrongAuth.timeoutForUser";
+    private static final String NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG =
+            "LockSettingsPrimaryAuth.nonStrongBiometricTimeoutForUser";
+    private static final String NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG =
+            "LockSettingsPrimaryAuth.nonStrongBiometricIdleTimeoutForUser";
+
+    /**
+     * Default and maximum timeout in milliseconds after which unlocking with weak auth times out,
+     * i.e. the user has to use a strong authentication method like password, PIN or pattern.
+     */
+    public static final long DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24h
+    public static final long DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS =
+            4 * 60 * 60 * 1000; // 4h
 
     private final RemoteCallbackList<IStrongAuthTracker> mTrackers = new RemoteCallbackList<>();
     private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
+    private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser = new SparseBooleanArray();
     private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
             mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
+    // Track non-strong biometric timeout
+    private final ArrayMap<Integer, NonStrongBiometricTimeoutAlarmListener>
+            mNonStrongBiometricTimeoutAlarmListener = new ArrayMap<>();
+    // Track non-strong biometric idle timeout
+    private final ArrayMap<Integer, NonStrongBiometricIdleTimeoutAlarmListener>
+            mNonStrongBiometricIdleTimeoutAlarmListener = new ArrayMap<>();
+
     private final int mDefaultStrongAuthFlags;
+    private final boolean mDefaultIsNonStrongBiometricAllowed = true;
+
     private final Context mContext;
 
     private AlarmManager mAlarmManager;
@@ -80,6 +109,17 @@
                 Slog.e(TAG, "Exception while adding StrongAuthTracker.", e);
             }
         }
+
+        for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+            int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+            boolean value = mIsNonStrongBiometricAllowedForUser.valueAt(i);
+            try {
+                tracker.onIsNonStrongBiometricAllowedChanged(value, key);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Exception while adding StrongAuthTracker: "
+                        + "IsNonStrongBiometricAllowedChanged.", e);
+            }
+        }
     }
 
     private void handleRemoveStrongAuthTracker(IStrongAuthTracker tracker) {
@@ -134,6 +174,13 @@
             mStrongAuthForUser.removeAt(index);
             notifyStrongAuthTrackers(mDefaultStrongAuthFlags, userId);
         }
+
+        index = mIsNonStrongBiometricAllowedForUser.indexOfKey(userId);
+        if (index >= 0) {
+            mIsNonStrongBiometricAllowedForUser.removeAt(index);
+            notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(
+                    mDefaultIsNonStrongBiometricAllowed, userId);
+        }
     }
 
     private void handleScheduleStrongAuthTimeout(int userId) {
@@ -151,6 +198,125 @@
         // schedule a new alarm listener for the user
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
                 alarm, mHandler);
+
+        // cancel current non-strong biometric alarm listener for the user (if there was one)
+        cancelNonStrongBiometricAlarmListener(userId);
+        // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+        cancelNonStrongBiometricIdleAlarmListener(userId);
+        // re-allow unlock with non-strong biometrics
+        setIsNonStrongBiometricAllowed(true, userId);
+    }
+
+    private void handleScheduleNonStrongBiometricTimeout(int userId) {
+        if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricTimeout for userId=" + userId);
+        long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
+        NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
+                .get(userId);
+        if (alarm != null) {
+            // Unlock with non-strong biometric will not affect the existing non-strong biometric
+            // timeout alarm
+            if (DEBUG) {
+                Slog.d(TAG, "There is an existing alarm for non-strong biometric"
+                        + " fallback timeout, so do not re-schedule");
+            }
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Schedule a new alarm for non-strong biometric fallback timeout");
+            }
+            alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
+            mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
+            // schedule a new alarm listener for the user
+            mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+                    NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
+        }
+
+        // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+        cancelNonStrongBiometricIdleAlarmListener(userId);
+    }
+
+    private void handleStrongBiometricUnlock(int userId) {
+        if (DEBUG) Slog.d(TAG, "handleStrongBiometricUnlock for userId=" + userId);
+        // cancel current non-strong biometric alarm listener for the user (if there was one)
+        cancelNonStrongBiometricAlarmListener(userId);
+        // cancel current non-strong biometric idle alarm listener for the user (if there was one)
+        cancelNonStrongBiometricIdleAlarmListener(userId);
+        // re-allow unlock with non-strong biometrics
+        setIsNonStrongBiometricAllowed(true, userId);
+    }
+
+    private void cancelNonStrongBiometricAlarmListener(int userId) {
+        if (DEBUG) Slog.d(TAG, "cancelNonStrongBiometricAlarmListener for userId=" + userId);
+        NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
+                .get(userId);
+        if (alarm != null) {
+            if (DEBUG) Slog.d(TAG, "Cancel alarm for non-strong biometric fallback timeout");
+            mAlarmManager.cancel(alarm);
+            // need to remove the alarm when cancelled by primary auth or strong biometric
+            mNonStrongBiometricTimeoutAlarmListener.remove(userId);
+        }
+    }
+
+    private void cancelNonStrongBiometricIdleAlarmListener(int userId) {
+        if (DEBUG) Slog.d(TAG, "cancelNonStrongBiometricIdleAlarmListener for userId=" + userId);
+        // cancel idle alarm listener by any unlocks (i.e. primary auth, strong biometric,
+        // non-strong biometric)
+        NonStrongBiometricIdleTimeoutAlarmListener alarm =
+                mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
+        if (alarm != null) {
+            if (DEBUG) Slog.d(TAG, "Cancel alarm for non-strong biometric idle timeout");
+            mAlarmManager.cancel(alarm);
+        }
+    }
+
+    private void setIsNonStrongBiometricAllowed(boolean allowed, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "setIsNonStrongBiometricAllowed for allowed=" + allowed
+                    + ", userId=" + userId);
+        }
+        if (userId == UserHandle.USER_ALL) {
+            for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+                int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+                setIsNonStrongBiometricAllowedOneUser(allowed, key);
+            }
+        } else {
+            setIsNonStrongBiometricAllowedOneUser(allowed, userId);
+        }
+    }
+
+    private void setIsNonStrongBiometricAllowedOneUser(boolean allowed, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "setIsNonStrongBiometricAllowedOneUser for allowed=" + allowed
+                    + ", userId=" + userId);
+        }
+        boolean oldValue = mIsNonStrongBiometricAllowedForUser.get(userId,
+                mDefaultIsNonStrongBiometricAllowed);
+        if (allowed != oldValue) {
+            if (DEBUG) {
+                Slog.d(TAG, "mIsNonStrongBiometricAllowedForUser value changed:"
+                        + " oldValue=" + oldValue + ", allowed=" + allowed);
+            }
+            mIsNonStrongBiometricAllowedForUser.put(userId, allowed);
+            notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(allowed, userId);
+        }
+    }
+
+    private void handleScheduleNonStrongBiometricIdleTimeout(int userId) {
+        if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricIdleTimeout for userId=" + userId);
+        long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
+        // cancel current alarm listener for the user (if there was one)
+        NonStrongBiometricIdleTimeoutAlarmListener alarm =
+                mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
+        if (alarm != null) {
+            if (DEBUG) Slog.d(TAG, "Cancel existing alarm for non-strong biometric idle timeout");
+            mAlarmManager.cancel(alarm);
+        } else {
+            alarm = new NonStrongBiometricIdleTimeoutAlarmListener(userId);
+            mNonStrongBiometricIdleTimeoutAlarmListener.put(userId, alarm);
+        }
+        // schedule a new alarm listener for the user
+        if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+                NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
     }
 
     private void notifyStrongAuthTrackers(int strongAuthReason, int userId) {
@@ -170,6 +336,29 @@
         }
     }
 
+    private void notifyStrongAuthTrackersForIsNonStrongBiometricAllowed(boolean allowed,
+            int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifyStrongAuthTrackersForIsNonStrongBiometricAllowed"
+                    + " for allowed=" + allowed + ", userId=" + userId);
+        }
+        int i = mTrackers.beginBroadcast();
+        try {
+            while (i > 0) {
+                i--;
+                try {
+                    mTrackers.getBroadcastItem(i).onIsNonStrongBiometricAllowedChanged(
+                            allowed, userId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception while notifying StrongAuthTracker: "
+                            + "IsNonStrongBiometricAllowedChanged.", e);
+                }
+            }
+        } finally {
+            mTrackers.finishBroadcast();
+        }
+    }
+
     public void registerStrongAuthTracker(IStrongAuthTracker tracker) {
         mHandler.obtainMessage(MSG_REGISTER_TRACKER, tracker).sendToTarget();
     }
@@ -207,11 +396,45 @@
         requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
     }
 
+    /**
+     * Report successful unlocking with primary auth
+     */
     public void reportSuccessfulStrongAuthUnlock(int userId) {
         final int argNotUsed = 0;
         mHandler.obtainMessage(MSG_SCHEDULE_STRONG_AUTH_TIMEOUT, userId, argNotUsed).sendToTarget();
     }
 
+    /**
+     * Report successful unlocking with biometric
+     */
+    public void reportSuccessfulBiometricUnlock(boolean isStrongBiometric, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "reportSuccessfulBiometricUnlock for isStrongBiometric="
+                    + isStrongBiometric + ", userId=" + userId);
+        }
+        final int argNotUsed = 0;
+        if (isStrongBiometric) { // unlock with strong biometric
+            mHandler.obtainMessage(MSG_STRONG_BIOMETRIC_UNLOCK, userId, argNotUsed)
+                    .sendToTarget();
+        } else { // unlock with non-strong biometric (i.e. weak or convenience)
+            mHandler.obtainMessage(MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT, userId, argNotUsed)
+                    .sendToTarget();
+        }
+    }
+
+    /**
+     * Schedule idle timeout for non-strong biometric (i.e. weak or convenience)
+     */
+    public void scheduleNonStrongBiometricIdleTimeout(int userId) {
+        if (DEBUG) Slog.d(TAG, "scheduleNonStrongBiometricIdleTimeout for userId=" + userId);
+        final int argNotUsed = 0;
+        mHandler.obtainMessage(MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT, userId, argNotUsed)
+                .sendToTarget();
+    }
+
+    /**
+     * Alarm of fallback timeout for primary auth
+     */
     private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
 
         private final int mUserId;
@@ -226,6 +449,41 @@
         }
     }
 
+    /**
+     * Alarm of fallback timeout for non-strong biometric (i.e. weak or convenience)
+     */
+    private class NonStrongBiometricTimeoutAlarmListener implements OnAlarmListener {
+
+        private final int mUserId;
+
+        NonStrongBiometricTimeoutAlarmListener(int userId) {
+            mUserId = userId;
+        }
+
+        @Override
+        public void onAlarm() {
+            requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT, mUserId);
+        }
+    }
+
+    /**
+     * Alarm of idle timeout for non-strong biometric (i.e. weak or convenience biometric)
+     */
+    private class NonStrongBiometricIdleTimeoutAlarmListener implements OnAlarmListener {
+
+        private final int mUserId;
+
+        NonStrongBiometricIdleTimeoutAlarmListener(int userId) {
+            mUserId = userId;
+        }
+
+        @Override
+        public void onAlarm() {
+            // disallow unlock with non-strong biometrics
+            setIsNonStrongBiometricAllowed(false, mUserId);
+        }
+    }
+
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -248,8 +506,38 @@
                 case MSG_NO_LONGER_REQUIRE_STRONG_AUTH:
                     handleNoLongerRequireStrongAuth(msg.arg1, msg.arg2);
                     break;
+                case MSG_SCHEDULE_NON_STRONG_BIOMETRIC_TIMEOUT:
+                    handleScheduleNonStrongBiometricTimeout(msg.arg1);
+                    break;
+                case MSG_STRONG_BIOMETRIC_UNLOCK:
+                    handleStrongBiometricUnlock(msg.arg1);
+                    break;
+                case MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT:
+                    handleScheduleNonStrongBiometricIdleTimeout(msg.arg1);
+                    break;
             }
         }
     };
 
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("PrimaryAuthFlags state:");
+        pw.increaseIndent();
+        for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+            final int key = mStrongAuthForUser.keyAt(i);
+            final int value = mStrongAuthForUser.valueAt(i);
+            pw.println("userId=" + key + ", primaryAuthFlags=" + Integer.toHexString(value));
+        }
+        pw.println();
+        pw.decreaseIndent();
+
+        pw.println("NonStrongBiometricAllowed state:");
+        pw.increaseIndent();
+        for (int i = 0; i < mIsNonStrongBiometricAllowedForUser.size(); i++) {
+            final int key = mIsNonStrongBiometricAllowedForUser.keyAt(i);
+            final boolean value = mIsNonStrongBiometricAllowedForUser.valueAt(i);
+            pw.println("userId=" + key + ", allowed=" + value);
+        }
+        pw.println();
+        pw.decreaseIndent();
+    }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 9b1824f..f144405 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -53,15 +53,15 @@
 
     public abstract void requestCreateSession(String packageName, String routeId, long requestId,
             @Nullable Bundle sessionHints);
-    public abstract void releaseSession(String sessionId);
+    public abstract void releaseSession(String sessionId, long requestId);
     public abstract void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference);
 
-    public abstract void selectRoute(String sessionId, String routeId);
-    public abstract void deselectRoute(String sessionId, String routeId);
-    public abstract void transferToRoute(String sessionId, String routeId);
+    public abstract void selectRoute(String sessionId, String routeId, long requestId);
+    public abstract void deselectRoute(String sessionId, String routeId, long requestId);
+    public abstract void transferToRoute(String sessionId, String routeId, long requestId);
 
-    public abstract void setRouteVolume(String routeId, int volume);
-    public abstract void setSessionVolume(String sessionId, int volume);
+    public abstract void setRouteVolume(String routeId, int volume, long requestId);
+    public abstract void setSessionVolume(String sessionId, int volume, long requestId);
 
     @NonNull
     public String getUniqueId() {
@@ -116,5 +116,6 @@
                 @NonNull RoutingSessionInfo sessionInfo);
         void onSessionReleased(@NonNull MediaRoute2Provider provider,
                 @NonNull RoutingSessionInfo sessionInfo);
+        void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason);
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 60934e0..e64776c 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -87,9 +87,9 @@
     }
 
     @Override
-    public void releaseSession(String sessionId) {
+    public void releaseSession(String sessionId, long requestId) {
         if (mConnectionReady) {
-            mActiveConnection.releaseSession(sessionId);
+            mActiveConnection.releaseSession(sessionId, requestId);
             updateBinding();
         }
     }
@@ -103,38 +103,38 @@
     }
 
     @Override
-    public void selectRoute(String sessionId, String routeId) {
+    public void selectRoute(String sessionId, String routeId, long requestId) {
         if (mConnectionReady) {
-            mActiveConnection.selectRoute(sessionId, routeId);
+            mActiveConnection.selectRoute(sessionId, routeId, requestId);
         }
     }
 
     @Override
-    public void deselectRoute(String sessionId, String routeId) {
+    public void deselectRoute(String sessionId, String routeId, long requestId) {
         if (mConnectionReady) {
-            mActiveConnection.deselectRoute(sessionId, routeId);
+            mActiveConnection.deselectRoute(sessionId, routeId, requestId);
         }
     }
 
     @Override
-    public void transferToRoute(String sessionId, String routeId) {
+    public void transferToRoute(String sessionId, String routeId, long requestId) {
         if (mConnectionReady) {
-            mActiveConnection.transferToRoute(sessionId, routeId);
+            mActiveConnection.transferToRoute(sessionId, routeId, requestId);
         }
     }
 
     @Override
-    public void setRouteVolume(String routeId, int volume) {
+    public void setRouteVolume(String routeId, int volume, long requestId) {
         if (mConnectionReady) {
-            mActiveConnection.setRouteVolume(routeId, volume);
+            mActiveConnection.setRouteVolume(routeId, volume, requestId);
             updateBinding();
         }
     }
 
     @Override
-    public void setSessionVolume(String sessionId, int volume) {
+    public void setSessionVolume(String sessionId, int volume, long requestId) {
         if (mConnectionReady) {
-            mActiveConnection.setSessionVolume(sessionId, volume);
+            mActiveConnection.setSessionVolume(sessionId, volume, requestId);
             updateBinding();
         }
     }
@@ -333,8 +333,8 @@
             return;
         }
 
-        if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
-            Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_UNKNOWN");
+        if (requestId == MediaRoute2ProviderService.REQUEST_ID_NONE) {
+            Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_NONE");
             return;
         }
 
@@ -406,6 +406,19 @@
         mCallback.onSessionReleased(this, sessionInfo);
     }
 
+    private void onRequestFailed(Connection connection, long requestId, int reason) {
+        if (mActiveConnection != connection) {
+            return;
+        }
+
+        if (requestId == MediaRoute2ProviderService.REQUEST_ID_NONE) {
+            Slog.w(TAG, "onRequestFailed: Ignoring requestId REQUEST_ID_NONE");
+            return;
+        }
+
+        mCallback.onRequestFailed(this, requestId, reason);
+    }
+
     private void disconnect() {
         if (mActiveConnection != null) {
             mConnectionReady = false;
@@ -461,9 +474,9 @@
             }
         }
 
-        public void releaseSession(String sessionId) {
+        public void releaseSession(String sessionId, long requestId) {
             try {
-                mService.releaseSession(sessionId);
+                mService.releaseSession(sessionId, requestId);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "releaseSession: Failed to deliver request.");
             }
@@ -477,41 +490,41 @@
             }
         }
 
-        public void selectRoute(String sessionId, String routeId) {
+        public void selectRoute(String sessionId, String routeId, long requestId) {
             try {
-                mService.selectRoute(sessionId, routeId);
+                mService.selectRoute(sessionId, routeId, requestId);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "selectRoute: Failed to deliver request.", ex);
             }
         }
 
-        public void deselectRoute(String sessionId, String routeId) {
+        public void deselectRoute(String sessionId, String routeId, long requestId) {
             try {
-                mService.deselectRoute(sessionId, routeId);
+                mService.deselectRoute(sessionId, routeId, requestId);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "deselectRoute: Failed to deliver request.", ex);
             }
         }
 
-        public void transferToRoute(String sessionId, String routeId) {
+        public void transferToRoute(String sessionId, String routeId, long requestId) {
             try {
-                mService.transferToRoute(sessionId, routeId);
+                mService.transferToRoute(sessionId, routeId, requestId);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "transferToRoute: Failed to deliver request.", ex);
             }
         }
 
-        public void setRouteVolume(String routeId, int volume) {
+        public void setRouteVolume(String routeId, int volume, long requestId) {
             try {
-                mService.setRouteVolume(routeId, volume);
+                mService.setRouteVolume(routeId, volume, requestId);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "setRouteVolume: Failed to deliver request.", ex);
             }
         }
 
-        public void setSessionVolume(String sessionId, int volume) {
+        public void setSessionVolume(String sessionId, int volume, long requestId) {
             try {
-                mService.setSessionVolume(sessionId, volume);
+                mService.setSessionVolume(sessionId, volume, requestId);
             } catch (RemoteException ex) {
                 Slog.e(TAG, "setSessionVolume: Failed to deliver request.", ex);
             }
@@ -541,6 +554,10 @@
         void postSessionReleased(RoutingSessionInfo sessionInfo) {
             mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo));
         }
+
+        void postSessionReleased(long requestId, int reason) {
+            mHandler.post(() -> onRequestFailed(Connection.this, requestId, reason));
+        }
     }
 
     private static final class ServiceCallbackStub extends
@@ -594,5 +611,13 @@
                 connection.postSessionReleased(sessionInfo);
             }
         }
+
+        @Override
+        public void notifyRequestFailed(long requestId, int reason) {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                connection.postSessionReleased(requestId, reason);
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 3588916..e78a35c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
 import static android.media.MediaRouter2Utils.getOriginalId;
 import static android.media.MediaRouter2Utils.getProviderId;
 
@@ -30,7 +31,6 @@
 import android.media.IMediaRouter2Manager;
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
-import android.media.MediaRoute2ProviderService;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Binder;
@@ -70,6 +70,12 @@
     private static final String TAG = "MR2ServiceImpl";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
+    /**
+     * TODO: Change this with the real request ID from MediaRouter2 when
+     * MediaRouter2 needs to get notified for the failures.
+     */
+    private static final long DUMMY_REQUEST_ID = -1;
+
     private final Context mContext;
     private final Object mLock = new Object();
     final AtomicInteger mNextRouterOrManagerId = new AtomicInteger(1);
@@ -365,14 +371,14 @@
     }
 
     public void setRouteVolumeWithManager(IMediaRouter2Manager manager,
-            MediaRoute2Info route, int volume) {
+            MediaRoute2Info route, int volume, int requestId) {
         Objects.requireNonNull(manager, "manager must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                setRouteVolumeWithManagerLocked(manager, route, volume);
+                setRouteVolumeWithManagerLocked(manager, route, volume, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -397,7 +403,7 @@
     }
 
     public void selectRouteWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
-            MediaRoute2Info route) {
+            MediaRoute2Info route, int requestId) {
         Objects.requireNonNull(manager, "manager must not be null");
         if (TextUtils.isEmpty(uniqueSessionId)) {
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -407,7 +413,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                selectRouteWithManagerLocked(manager, uniqueSessionId, route);
+                selectRouteWithManagerLocked(manager, uniqueSessionId, route, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -415,7 +421,7 @@
     }
 
     public void deselectRouteWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
-            MediaRoute2Info route) {
+            MediaRoute2Info route, int requestId) {
         Objects.requireNonNull(manager, "manager must not be null");
         if (TextUtils.isEmpty(uniqueSessionId)) {
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -425,7 +431,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                deselectRouteWithManagerLocked(manager, uniqueSessionId, route);
+                deselectRouteWithManagerLocked(manager, uniqueSessionId, route, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -433,7 +439,7 @@
     }
 
     public void transferToRouteWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
-            MediaRoute2Info route) {
+            MediaRoute2Info route, int requestId) {
         Objects.requireNonNull(manager, "manager must not be null");
         if (TextUtils.isEmpty(uniqueSessionId)) {
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -443,7 +449,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                transferToRouteWithManagerLocked(manager, uniqueSessionId, route);
+                transferToRouteWithManagerLocked(manager, uniqueSessionId, route, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -451,7 +457,7 @@
     }
 
     public void setSessionVolumeWithManager(IMediaRouter2Manager manager,
-            String uniqueSessionId, int volume) {
+            String uniqueSessionId, int volume, int requestId) {
         Objects.requireNonNull(manager, "manager must not be null");
         if (TextUtils.isEmpty(uniqueSessionId)) {
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -460,14 +466,15 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                setSessionVolumeWithManagerLocked(manager, uniqueSessionId, volume);
+                setSessionVolumeWithManagerLocked(manager, uniqueSessionId, volume, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
-    public void releaseSessionWithManager(IMediaRouter2Manager manager, String uniqueSessionId) {
+    public void releaseSessionWithManager(IMediaRouter2Manager manager, String uniqueSessionId,
+            int requestId) {
         Objects.requireNonNull(manager, "manager must not be null");
         if (TextUtils.isEmpty(uniqueSessionId)) {
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
@@ -476,7 +483,7 @@
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                releaseSessionWithManagerLocked(manager, uniqueSessionId);
+                releaseSessionWithManagerLocked(manager, uniqueSessionId, requestId);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -572,7 +579,7 @@
                 obtainMessage(UserHandler::notifyPreferredFeaturesChangedToManagers,
                         routerRecord.mUserRecord.mHandler, routerRecord));
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::updateDiscoveryPreference,
+                obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
                         routerRecord.mUserRecord.mHandler));
     }
 
@@ -584,7 +591,7 @@
         if (routerRecord != null) {
             routerRecord.mUserRecord.mHandler.sendMessage(
                     obtainMessage(UserHandler::setRouteVolumeOnHandler,
-                            routerRecord.mUserRecord.mHandler, route, volume));
+                            routerRecord.mUserRecord.mHandler, route, volume, DUMMY_REQUEST_ID));
         }
     }
 
@@ -615,7 +622,8 @@
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::selectRouteOnHandler,
-                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+                        DUMMY_REQUEST_ID));
     }
 
     private void deselectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -629,7 +637,8 @@
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::deselectRouteOnHandler,
-                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+                        DUMMY_REQUEST_ID));
     }
 
     private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -643,7 +652,8 @@
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::transferToRouteOnHandler,
-                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+                        DUMMY_REQUEST_ID));
     }
 
     private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -657,7 +667,8 @@
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
-                        routerRecord.mUserRecord.mHandler, uniqueSessionId, volume));
+                        routerRecord.mUserRecord.mHandler, uniqueSessionId, volume,
+                        DUMMY_REQUEST_ID));
     }
 
     private void releaseSessionWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -671,7 +682,8 @@
 
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::releaseSessionOnHandler,
-                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId));
+                        routerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId,
+                        DUMMY_REQUEST_ID));
     }
 
     ////////////////////////////////////////////////////////////
@@ -744,16 +756,18 @@
     }
 
     private void setRouteVolumeWithManagerLocked(@NonNull IMediaRouter2Manager manager,
-            @NonNull MediaRoute2Info route, int volume) {
+            @NonNull MediaRoute2Info route, int volume, int requestId) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
         if (managerRecord == null) {
             return;
         }
+
+        long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::setRouteVolumeOnHandler,
-                        managerRecord.mUserRecord.mHandler, route, volume));
+                        managerRecord.mUserRecord.mHandler, route, volume, uniqueRequestId));
     }
 
     private void requestCreateSessionWithManagerLocked(@NonNull IMediaRouter2Manager manager,
@@ -778,7 +792,7 @@
     }
 
     private void selectRouteWithManagerLocked(@NonNull IMediaRouter2Manager manager,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, int requestId) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -790,13 +804,15 @@
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
                 .findRouterforSessionLocked(uniqueSessionId);
 
+        long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::selectRouteOnHandler,
-                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+                        uniqueRequestId));
     }
 
     private void deselectRouteWithManagerLocked(@NonNull IMediaRouter2Manager manager,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, int requestId) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -808,13 +824,15 @@
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
                 .findRouterforSessionLocked(uniqueSessionId);
 
+        long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::deselectRouteOnHandler,
-                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+                        uniqueRequestId));
     }
 
     private void transferToRouteWithManagerLocked(@NonNull IMediaRouter2Manager manager,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, int requestId) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -826,13 +844,15 @@
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
                 .findRouterforSessionLocked(uniqueSessionId);
 
+        long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::transferToRouteOnHandler,
-                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route));
+                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId, route,
+                        uniqueRequestId));
     }
 
     private void setSessionVolumeWithManagerLocked(@NonNull IMediaRouter2Manager manager,
-            @NonNull String uniqueSessionId, int volume) {
+            @NonNull String uniqueSessionId, int volume, int requestId) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -840,13 +860,15 @@
             return;
         }
 
+        long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::setSessionVolumeOnHandler,
-                        managerRecord.mUserRecord.mHandler, uniqueSessionId, volume));
+                        managerRecord.mUserRecord.mHandler, uniqueSessionId, volume,
+                        uniqueRequestId));
     }
 
     private void releaseSessionWithManagerLocked(@NonNull IMediaRouter2Manager manager,
-            @NonNull String uniqueSessionId) {
+            @NonNull String uniqueSessionId, int requestId) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -856,10 +878,15 @@
 
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
                 .findRouterforSessionLocked(uniqueSessionId);
+        if (routerRecord == null) {
+            return;
+        }
 
+        long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::releaseSessionOnHandler,
-                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId));
+                        managerRecord.mUserRecord.mHandler, routerRecord, uniqueSessionId,
+                        uniqueRequestId));
     }
 
     ////////////////////////////////////////////////////////////
@@ -1100,6 +1127,13 @@
                     this, provider, sessionInfo));
         }
 
+        @Override
+        public void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId,
+                int reason) {
+            sendMessage(PooledLambda.obtainMessage(UserHandler::onRequestFailedOnHandler,
+                    this, provider, requestId, reason));
+        }
+
         @Nullable
         public RouterRecord findRouterforSessionLocked(@NonNull String uniqueSessionId) {
             return mSessionToRouterMap.get(uniqueSessionId);
@@ -1195,7 +1229,7 @@
             if (provider == null) {
                 Slog.w(TAG, "Ignoring session creation request since no provider found for"
                         + " given route=" + route);
-                notifySessionCreationFailed(routerRecord, toOriginalRequestId(requestId));
+                notifySessionCreationFailedToRouter(routerRecord, toOriginalRequestId(requestId));
                 return;
             }
 
@@ -1210,7 +1244,7 @@
 
         // routerRecord can be null if the session is system's.
         private void selectRouteOnHandler(@Nullable RouterRecord routerRecord,
-                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, long requestId) {
             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
                     "selecting")) {
                 return;
@@ -1222,12 +1256,12 @@
             if (provider == null) {
                 return;
             }
-            provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
+            provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId(), requestId);
         }
 
         // routerRecord can be null if the session is system's.
         private void deselectRouteOnHandler(@Nullable RouterRecord routerRecord,
-                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, long requestId) {
             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
                     "deselecting")) {
                 return;
@@ -1239,12 +1273,13 @@
             if (provider == null) {
                 return;
             }
-            provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
+            provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId(),
+                    requestId);
         }
 
         // routerRecord can be null if the session is system's.
         private void transferToRouteOnHandler(@Nullable RouterRecord routerRecord,
-                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route, long requestId) {
             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
                     "transferring to")) {
                 return;
@@ -1256,7 +1291,8 @@
             if (provider == null) {
                 return;
             }
-            provider.transferToRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
+            provider.transferToRoute(getOriginalId(uniqueSessionId), route.getOriginalId(),
+                    requestId);
         }
 
         private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
@@ -1299,7 +1335,7 @@
         }
 
         private void releaseSessionOnHandler(@NonNull RouterRecord routerRecord,
-                @NonNull String uniqueSessionId) {
+                @NonNull String uniqueSessionId, long uniqueRequestId) {
             final RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
             if (matchingRecord != routerRecord) {
                 Slog.w(TAG, "Ignoring releasing session from non-matching router."
@@ -1329,14 +1365,14 @@
                 return;
             }
 
-            provider.releaseSession(sessionId);
+            provider.releaseSession(sessionId, uniqueRequestId);
         }
 
         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
                 @NonNull RoutingSessionInfo sessionInfo, long requestId) {
             notifySessionCreatedToManagers(getManagers(), sessionInfo);
 
-            if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+            if (requestId == REQUEST_ID_NONE) {
                 // The session is created without any matching request.
                 return;
             }
@@ -1362,7 +1398,7 @@
 
             if (sessionInfo == null) {
                 // Failed
-                notifySessionCreationFailed(matchingRequest.mRouterRecord,
+                notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
                         toOriginalRequestId(requestId));
                 return;
             }
@@ -1374,13 +1410,13 @@
                 Slog.w(TAG, "Created session doesn't match the original request."
                         + " originalRouteId=" + originalRouteId
                         + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
-                notifySessionCreationFailed(matchingRequest.mRouterRecord,
+                notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
                         toOriginalRequestId(requestId));
                 return;
             }
 
             // Succeeded
-            notifySessionCreated(matchingRequest.mRouterRecord,
+            notifySessionCreatedToRouter(matchingRequest.mRouterRecord,
                     sessionInfo, toOriginalRequestId(requestId));
             mSessionToRouterMap.put(sessionInfo.getId(), routerRecord);
         }
@@ -1405,7 +1441,7 @@
             }
 
             mSessionCreationRequests.remove(matchingRequest);
-            notifySessionCreationFailed(matchingRequest.mRouterRecord,
+            notifySessionCreationFailedToRouter(matchingRequest.mRouterRecord,
                     toOriginalRequestId(requestId));
         }
 
@@ -1429,7 +1465,7 @@
                 Slog.w(TAG, "No matching router found for session=" + sessionInfo);
                 return;
             }
-            notifySessionInfoChanged(routerRecord, sessionInfo);
+            notifySessionInfoChangedToRouter(routerRecord, sessionInfo);
         }
 
         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -1442,10 +1478,38 @@
                 Slog.w(TAG, "No matching router found for session=" + sessionInfo);
                 return;
             }
-            notifySessionReleased(routerRecord, sessionInfo);
+            notifySessionReleasedToRouter(routerRecord, sessionInfo);
         }
 
-        private void notifySessionCreated(@NonNull RouterRecord routerRecord,
+        private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
+                long requestId, int reason) {
+            final int managerId = toRouterOrManagerId(requestId);
+
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return;
+            }
+
+            ManagerRecord managerToNotifyFailure = null;
+            synchronized (service.mLock) {
+                for (ManagerRecord manager : mUserRecord.mManagerRecords) {
+                    if (manager.mManagerId == managerId) {
+                        managerToNotifyFailure = manager;
+                        break;
+                    }
+                }
+            }
+
+            if (managerToNotifyFailure == null) {
+                Slog.w(TAG, "No matching managerRecord found for managerId=" + managerId);
+                return;
+            }
+
+            notifyRequestFailedToManager(
+                    managerToNotifyFailure.mManager, toOriginalRequestId(requestId), reason);
+        }
+
+        private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
                 @NonNull RoutingSessionInfo sessionInfo, int requestId) {
             try {
                 routerRecord.mRouter.notifySessionCreated(sessionInfo, requestId);
@@ -1455,7 +1519,7 @@
             }
         }
 
-        private void notifySessionCreationFailed(@NonNull RouterRecord routerRecord,
+        private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
                 int requestId) {
             try {
                 routerRecord.mRouter.notifySessionCreated(/* sessionInfo= */ null, requestId);
@@ -1465,7 +1529,7 @@
             }
         }
 
-        private void notifySessionInfoChanged(@NonNull RouterRecord routerRecord,
+        private void notifySessionInfoChangedToRouter(@NonNull RouterRecord routerRecord,
                 @NonNull RoutingSessionInfo sessionInfo) {
             try {
                 routerRecord.mRouter.notifySessionInfoChanged(sessionInfo);
@@ -1475,7 +1539,7 @@
             }
         }
 
-        private void notifySessionReleased(@NonNull RouterRecord routerRecord,
+        private void notifySessionReleasedToRouter(@NonNull RouterRecord routerRecord,
                 @NonNull RoutingSessionInfo sessionInfo) {
             try {
                 routerRecord.mRouter.notifySessionReleased(sessionInfo);
@@ -1485,21 +1549,23 @@
             }
         }
 
-        private void setRouteVolumeOnHandler(@NonNull MediaRoute2Info route, int volume) {
+        private void setRouteVolumeOnHandler(@NonNull MediaRoute2Info route, int volume,
+                long requestId) {
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider != null) {
-                provider.setRouteVolume(route.getOriginalId(), volume);
+                provider.setRouteVolume(route.getOriginalId(), volume, requestId);
             }
         }
 
-        private void setSessionVolumeOnHandler(@NonNull String uniqueSessionId, int volume) {
+        private void setSessionVolumeOnHandler(@NonNull String uniqueSessionId, int volume,
+                long requestId) {
             final MediaRoute2Provider provider = findProvider(getProviderId(uniqueSessionId));
             if (provider == null) {
                 Slog.w(TAG, "setSessionVolume: couldn't find provider for session "
                         + "id=" + uniqueSessionId);
                 return;
             }
-            provider.setSessionVolume(getOriginalId(uniqueSessionId), volume);
+            provider.setSessionVolume(getOriginalId(uniqueSessionId), volume, requestId);
         }
 
         private List<IMediaRouter2> getRouters() {
@@ -1683,7 +1749,17 @@
             }
         }
 
-        private void updateDiscoveryPreference() {
+        private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
+                int requestId, int reason) {
+            try {
+                manager.notifyRequestFailed(requestId, reason);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify manager of the request failure."
+                        + " Manager probably died.", ex);
+            }
+        }
+
+        private void updateDiscoveryPreferenceOnHandler() {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
                 return;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 580fc52..a13ee10 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -543,8 +543,8 @@
     // Binder call
     @Override
     public void setRouteVolumeWithManager(IMediaRouter2Manager manager,
-            MediaRoute2Info route, int volume) {
-        mService2.setRouteVolumeWithManager(manager, route, volume);
+            MediaRoute2Info route, int volume, int requestId) {
+        mService2.setRouteVolumeWithManager(manager, route, volume, requestId);
     }
 
     // Binder call
@@ -557,35 +557,36 @@
     // Binder call
     @Override
     public void selectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
-            MediaRoute2Info route) {
-        mService2.selectRouteWithManager(manager, sessionId, route);
+            MediaRoute2Info route, int requestId) {
+        mService2.selectRouteWithManager(manager, sessionId, route, requestId);
     }
 
     // Binder call
     @Override
     public void deselectRouteWithManager(IMediaRouter2Manager manager, String sessionId,
-            MediaRoute2Info route) {
-        mService2.deselectRouteWithManager(manager, sessionId, route);
+            MediaRoute2Info route, int requestId) {
+        mService2.deselectRouteWithManager(manager, sessionId, route, requestId);
     }
 
     // Binder call
     @Override
     public void transferToRouteWithManager(IMediaRouter2Manager manager, String sessionId,
-            MediaRoute2Info route) {
-        mService2.transferToRouteWithManager(manager, sessionId, route);
+            MediaRoute2Info route, int requestId) {
+        mService2.transferToRouteWithManager(manager, sessionId, route, requestId);
     }
 
     // Binder call
     @Override
     public void setSessionVolumeWithManager(IMediaRouter2Manager manager,
-            String sessionId, int volume) {
-        mService2.setSessionVolumeWithManager(manager, sessionId, volume);
+            String sessionId, int volume, int requestId) {
+        mService2.setSessionVolumeWithManager(manager, sessionId, volume, requestId);
     }
 
     // Binder call
     @Override
-    public void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId) {
-        mService2.releaseSessionWithManager(manager, sessionId);
+    public void releaseSessionWithManager(IMediaRouter2Manager manager, String sessionId,
+            int requestId) {
+        mService2.releaseSessionWithManager(manager, sessionId, requestId);
     }
 
     void restoreBluetoothA2dp() {
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index f7e1398..ce72a8a 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -130,26 +130,27 @@
     }
 
     @Override
-    public void releaseSession(String sessionId) {
+    public void releaseSession(String sessionId, long requestId) {
         // Do nothing
     }
+
     @Override
     public void updateDiscoveryPreference(RouteDiscoveryPreference discoveryPreference) {
         // Do nothing
     }
 
     @Override
-    public void selectRoute(String sessionId, String routeId) {
+    public void selectRoute(String sessionId, String routeId, long requestId) {
         // Do nothing since we don't support multiple BT yet.
     }
 
     @Override
-    public void deselectRoute(String sessionId, String routeId) {
+    public void deselectRoute(String sessionId, String routeId, long requestId) {
         // Do nothing since we don't support multiple BT yet.
     }
 
     @Override
-    public void transferToRoute(String sessionId, String routeId) {
+    public void transferToRoute(String sessionId, String routeId, long requestId) {
         if (TextUtils.equals(routeId, mDefaultRoute.getId())) {
             mBtRouteProvider.transferTo(null);
         } else {
@@ -158,7 +159,7 @@
     }
 
     @Override
-    public void setRouteVolume(String routeId, int volume) {
+    public void setRouteVolume(String routeId, int volume, long requestId) {
         if (!TextUtils.equals(routeId, mSelectedRouteId)) {
             return;
         }
@@ -166,7 +167,7 @@
     }
 
     @Override
-    public void setSessionVolume(String sessionId, int volume) {
+    public void setSessionVolume(String sessionId, int volume, long requestId) {
         // Do nothing since we don't support grouping volume yet.
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index fce10e6..c301cd2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -27,4 +27,6 @@
             String tag, int id, int userId);
 
     void removeForegroundServiceFlagFromNotification(String pkg, int notificationId, int userId);
+
+    void onConversationRemoved(String pkg, int uid, String conversationId);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0d402e5..475f229 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -772,7 +772,7 @@
                         parser, mAllowedManagedServicePackages, forRestore, userId);
                 migratedManagedServices = true;
             } else if (mSnoozeHelper.XML_TAG_NAME.equals(parser.getName())) {
-                mSnoozeHelper.readXml(parser);
+                mSnoozeHelper.readXml(parser, System.currentTimeMillis());
             }
             if (LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG.equals(parser.getName())) {
                 if (forRestore && userId != UserHandle.USER_SYSTEM) {
@@ -2322,6 +2322,8 @@
             mConditionProviders.onBootPhaseAppsCanStart();
             mHistoryManager.onBootPhaseAppsCanStart();
             registerDeviceConfigChange();
+        } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+            mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
         }
     }
 
@@ -5474,6 +5476,11 @@
             });
         }
 
+        @Override
+        public void onConversationRemoved(String pkg, int uid, String conversationId) {
+            onConversationRemovedInternal(pkg, uid, conversationId);
+        }
+
         @GuardedBy("mNotificationLock")
         private void removeForegroundServiceFlagLocked(NotificationRecord r) {
             if (r == null) {
@@ -5676,7 +5683,7 @@
         mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
     }
 
-    public void onConversationRemoved(String pkg, int uid, String conversationId) {
+    private void onConversationRemovedInternal(String pkg, int uid, String conversationId) {
         checkCallerIsSystem();
         Preconditions.checkStringNotEmpty(pkg);
         Preconditions.checkStringNotEmpty(conversationId);
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index bae1dd3..d60c291 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -36,6 +36,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.util.XmlUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -43,9 +44,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.sql.Array;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -172,7 +171,7 @@
             for (int i = 0; i < allRecords.size(); i++) {
                 NotificationRecord r = allRecords.valueAt(i);
                 String currentGroupKey = r.getSbn().getGroup();
-                if (currentGroupKey.equals(groupKey)) {
+                if (Objects.equals(currentGroupKey, groupKey)) {
                     records.add(r);
                 }
             }
@@ -217,7 +216,7 @@
 
         snooze(record);
         scheduleRepost(pkg, key, userId, duration);
-        Long activateAt = System.currentTimeMillis() + duration;
+        Long activateAt = SystemClock.elapsedRealtime() + duration;
         synchronized (mPersistedSnoozedNotifications) {
             storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, activateAt);
         }
@@ -244,8 +243,6 @@
         }
         storeRecord(record.getSbn().getPackageName(), record.getKey(),
                 userId, mSnoozedNotifications, record);
-        mPackages.put(record.getKey(), record.getSbn().getPackageName());
-        mUsers.put(record.getKey(), userId);
     }
 
     private <T> void storeRecord(String pkg, String key, Integer userId,
@@ -258,6 +255,8 @@
         keyToValue.put(key, object);
         targets.put(getPkgKey(userId, pkg), keyToValue);
 
+        mPackages.put(key, pkg);
+        mUsers.put(key, userId);
     }
 
     private <T> T removeRecord(String pkg, String key, Integer userId,
@@ -425,12 +424,34 @@
                 PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
+    public void scheduleRepostsForPersistedNotifications(long currentTime) {
+        for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) {
+            for (int i = 0; i < snoozed.size(); i++) {
+                String key = snoozed.keyAt(i);
+                Long time = snoozed.valueAt(i);
+                String pkg = mPackages.get(key);
+                Integer userId = mUsers.get(key);
+                if (time == null || pkg == null || userId == null) {
+                    Slog.w(TAG, "data out of sync: " + time + "|" + pkg + "|" + userId);
+                    continue;
+                }
+                if (time != null && time > currentTime) {
+                    scheduleRepostAtTime(pkg, key, userId, time);
+                }
+            }
+
+        }
+    }
+
     private void scheduleRepost(String pkg, String key, int userId, long duration) {
+        scheduleRepostAtTime(pkg, key, userId, SystemClock.elapsedRealtime() + duration);
+    }
+
+    private void scheduleRepostAtTime(String pkg, String key, int userId, long time) {
         long identity = Binder.clearCallingIdentity();
         try {
             final PendingIntent pi = createPendingIntent(pkg, key, userId);
             mAm.cancel(pi);
-            long time = SystemClock.elapsedRealtime() + duration;
             if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time));
             mAm.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, time, pi);
         } finally {
@@ -496,6 +517,7 @@
     private interface Inserter<T> {
         void insert(T t) throws IOException;
     }
+
     private <T> void writeXml(XmlSerializer out,
             ArrayMap<String, ArrayMap<String, T>> targets, String tag,
             Inserter<T> attributeInserter)
@@ -503,12 +525,13 @@
         synchronized (targets) {
             final int M = targets.size();
             for (int i = 0; i < M; i++) {
-                String userIdPkgKey = targets.keyAt(i);
                 // T is a String (snoozed until context) or Long (snoozed until time)
                 ArrayMap<String, T> keyToValue = targets.valueAt(i);
                 for (int j = 0; j < keyToValue.size(); j++) {
                     String key = keyToValue.keyAt(j);
                     T value = keyToValue.valueAt(j);
+                    String pkg = mPackages.get(key);
+                    Integer userId = mUsers.get(key);
 
                     out.startTag(null, tag);
 
@@ -518,8 +541,7 @@
                             XML_SNOOZED_NOTIFICATION_VERSION);
                     out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
 
-                    String pkg = mPackages.get(key);
-                    int userId = mUsers.get(key);
+
                     out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
                     out.attribute(null, XML_SNOOZED_NOTIFICATION_USER_ID,
                             String.valueOf(userId));
@@ -530,7 +552,7 @@
         }
     }
 
-    protected void readXml(XmlPullParser parser)
+    protected void readXml(XmlPullParser parser, long currentTime)
             throws XmlPullParserException, IOException {
         int type;
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -547,16 +569,15 @@
                 try {
                     final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY);
                     final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG);
-                    final int userId = Integer.parseInt(
-                            parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_USER_ID));
+                    final int userId = XmlUtils.readIntAttribute(
+                            parser, XML_SNOOZED_NOTIFICATION_USER_ID, UserHandle.USER_ALL);
                     if (tag.equals(XML_SNOOZED_NOTIFICATION)) {
-                        final Long time = Long.parseLong(
-                                parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_TIME));
-                        if (time > System.currentTimeMillis()) { //only read new stuff
+                        final Long time = XmlUtils.readLongAttribute(
+                                parser, XML_SNOOZED_NOTIFICATION_TIME, 0);
+                        if (time > currentTime) { //only read new stuff
                             synchronized (mPersistedSnoozedNotifications) {
                                 storeRecord(pkg, key, userId, mPersistedSnoozedNotifications, time);
                             }
-                            scheduleRepost(pkg, key, userId, time - System.currentTimeMillis());
                         }
                     }
                     if (tag.equals(XML_SNOOZED_NOTIFICATION_CONTEXT)) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 8ad3e9d..f37af3a 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -234,11 +234,11 @@
     }
 
     public void moveCompleteApp(String fromUuid, String toUuid, String packageName,
-            String dataAppName, int appId, String seInfo, int targetSdkVersion,
+            int appId, String seInfo, int targetSdkVersion,
             String fromCodePath) throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
-            mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, dataAppName, appId, seInfo,
+            mInstalld.moveCompleteApp(fromUuid, toUuid, packageName, appId, seInfo,
                     targetSdkVersion, fromCodePath);
         } catch (Exception e) {
             throw InstallerException.from(e);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 261caba..8031eaa 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -72,6 +72,7 @@
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -134,6 +135,8 @@
 
         private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
 
+        private final ShortcutChangeHandler mShortcutChangeHandler;
+
         private final Handler mCallbackHandler;
 
         private PackageInstallerService mPackageInstallerService;
@@ -153,6 +156,8 @@
             mShortcutServiceInternal = Objects.requireNonNull(
                     LocalServices.getService(ShortcutServiceInternal.class));
             mShortcutServiceInternal.addListener(mPackageMonitor);
+            mShortcutChangeHandler = new ShortcutChangeHandler(mUserManagerInternal);
+            mShortcutServiceInternal.addShortcutChangeCallback(mShortcutChangeHandler);
             mCallbackHandler = BackgroundThread.getHandler();
             mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
         }
@@ -720,12 +725,37 @@
         @Override
         public void registerShortcutChangeCallback(String callingPackage, long changedSince,
                 String packageName, List shortcutIds, List<LocusId> locusIds,
-                ComponentName componentName, int flags, IShortcutChangeCallback callback,
-                int callbackId) {
+                ComponentName componentName, int flags, IShortcutChangeCallback callback) {
+            ensureShortcutPermission(callingPackage);
+
+            if (shortcutIds != null && packageName == null) {
+                throw new IllegalArgumentException(
+                        "To query by shortcut ID, package name must also be set");
+            }
+            if (locusIds != null && packageName == null) {
+                throw new IllegalArgumentException(
+                        "To query by locus ID, package name must also be set");
+            }
+
+            UserHandle user = UserHandle.of(injectCallingUserId());
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                    == PackageManager.PERMISSION_GRANTED) {
+                user = null;
+            }
+
+            // TODO: When ShortcutQueryWrapper (ag/10323729) is available, pass that directly.
+            ShortcutChangeHandler.QueryInfo query = new ShortcutChangeHandler.QueryInfo(
+                    changedSince, packageName, shortcutIds, locusIds, componentName, flags, user);
+            mShortcutChangeHandler.addShortcutChangeCallback(callback, query);
         }
 
         @Override
-        public void unregisterShortcutChangeCallback(String callingPackage, int callbackId) {
+        public void unregisterShortcutChangeCallback(String callingPackage,
+                IShortcutChangeCallback callback) {
+            ensureShortcutPermission(callingPackage);
+
+            mShortcutChangeHandler.removeShortcutChangeCallback(callback);
         }
 
         @Override
@@ -1005,6 +1035,153 @@
             mCallbackHandler.post(r);
         }
 
+        public static class ShortcutChangeHandler implements LauncherApps.ShortcutChangeCallback {
+
+            static class QueryInfo {
+                final long mChangedSince;
+                final String mPackage;
+                final List<String> mShortcutIds;
+                final List<LocusId> mLocusIds;
+                final ComponentName mActivity;
+                final int mQueryFlags;
+                final UserHandle mCallbackUser;
+
+                QueryInfo(long changedSince, String packageName, List<String> shortcutIds,
+                        List<LocusId> locusIds, ComponentName activity, int flags,
+                        UserHandle callbackUser) {
+                    mChangedSince = changedSince;
+                    mPackage = packageName;
+                    mShortcutIds = shortcutIds;
+                    mLocusIds = locusIds;
+                    mActivity = activity;
+                    mQueryFlags = flags;
+                    mCallbackUser = callbackUser;
+                }
+            }
+
+            private final UserManagerInternal mUserManagerInternal;
+
+            ShortcutChangeHandler(UserManagerInternal userManager) {
+                mUserManagerInternal = userManager;
+            }
+
+            private final RemoteCallbackList<IShortcutChangeCallback> mCallbacks =
+                    new RemoteCallbackList<>();
+
+            public synchronized void addShortcutChangeCallback(IShortcutChangeCallback callback,
+                    QueryInfo query) {
+                mCallbacks.unregister(callback);
+                mCallbacks.register(callback, query);
+            }
+
+            public synchronized void removeShortcutChangeCallback(
+                    IShortcutChangeCallback callback) {
+                mCallbacks.unregister(callback);
+            }
+
+            @Override
+            public void onShortcutsAddedOrUpdated(String packageName, List<ShortcutInfo> shortcuts,
+                    UserHandle user) {
+                onShortcutEvent(packageName, shortcuts, user, false);
+            }
+
+            @Override
+            public void onShortcutsRemoved(String packageName, List<ShortcutInfo> shortcuts,
+                    UserHandle user) {
+                onShortcutEvent(packageName, shortcuts, user, true);
+            }
+
+            private void onShortcutEvent(String packageName,
+                    List<ShortcutInfo> shortcuts, UserHandle user, boolean shortcutsRemoved) {
+                int count = mCallbacks.beginBroadcast();
+
+                for (int i = 0; i < count; i++) {
+                    final IShortcutChangeCallback callback = mCallbacks.getBroadcastItem(i);
+                    final QueryInfo query = (QueryInfo) mCallbacks.getBroadcastCookie(i);
+
+                    if (query.mCallbackUser != null && !hasUserAccess(query.mCallbackUser, user)) {
+                        // Callback owner does not have access to the shortcuts' user.
+                        continue;
+                    }
+
+                    // Filter the list by query, if any matches exists, send via callback.
+                    List<ShortcutInfo> matchedList =
+                            filterShortcutsByQuery(packageName, shortcuts, query);
+                    if (!CollectionUtils.isEmpty(matchedList)) {
+                        try {
+                            if (shortcutsRemoved) {
+                                callback.onShortcutsRemoved(packageName, matchedList, user);
+                            } else {
+                                callback.onShortcutsAddedOrUpdated(packageName, matchedList, user);
+                            }
+                        } catch (RemoteException e) {
+                            // The RemoteCallbackList will take care of removing the dead object.
+                        }
+                    }
+                }
+
+                mCallbacks.finishBroadcast();
+            }
+
+            public static List<ShortcutInfo> filterShortcutsByQuery(String packageName,
+                    List<ShortcutInfo> shortcuts, QueryInfo query) {
+                if (query.mPackage != null && query.mPackage != packageName) {
+                    return null;
+                }
+
+                List<ShortcutInfo> matches = new ArrayList<>();
+
+                final boolean matchDynamic =
+                        (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_DYNAMIC) != 0;
+                final boolean matchPinned =
+                        (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_PINNED) != 0;
+                final boolean matchManifest =
+                        (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_MANIFEST) != 0;
+                final boolean matchCached =
+                        (query.mQueryFlags & ShortcutQuery.FLAG_MATCH_CACHED) != 0;
+                final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0)
+                        | (matchPinned ? ShortcutInfo.FLAG_PINNED : 0)
+                        | (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
+                        | (matchCached ? ShortcutInfo.FLAG_CACHED : 0);
+
+                for (int i = 0; i < shortcuts.size(); i++) {
+                    final ShortcutInfo si = shortcuts.get(i);
+
+                    if (query.mActivity != null && !query.mActivity.equals(si.getActivity())) {
+                        continue;
+                    }
+
+                    if (query.mChangedSince != 0
+                            && query.mChangedSince > si.getLastChangedTimestamp()) {
+                        continue;
+                    }
+
+                    if (query.mShortcutIds != null && !query.mShortcutIds.contains(si.getId())) {
+                        continue;
+                    }
+
+                    if (query.mLocusIds != null && !query.mLocusIds.contains(si.getLocusId())) {
+                        continue;
+                    }
+
+                    if ((shortcutFlags & si.getFlags()) != 0) {
+                        matches.add(si);
+                    }
+                }
+
+                return matches;
+            }
+
+            private boolean hasUserAccess(UserHandle callbackUser, UserHandle shortcutUser) {
+                final int callbackUserId = callbackUser.getIdentifier();
+                final int shortcutUserId = shortcutUser.getIdentifier();
+
+                if (shortcutUser == callbackUser) return true;
+                return mUserManagerInternal.isProfileAccessible(callbackUserId, shortcutUserId,
+                        null, false);
+            }
+        }
+
         private class MyPackageMonitor extends PackageMonitor implements ShortcutChangeListener {
 
             // TODO Simplify with lambdas.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 92507e5..14964b5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13991,20 +13991,18 @@
         final String fromUuid;
         final String toUuid;
         final String packageName;
-        final String dataAppName;
         final int appId;
         final String seinfo;
         final int targetSdkVersion;
         final String fromCodePath;
 
         public MoveInfo(int moveId, String fromUuid, String toUuid, String packageName,
-                String dataAppName, int appId, String seinfo, int targetSdkVersion,
+                int appId, String seinfo, int targetSdkVersion,
                 String fromCodePath) {
             this.moveId = moveId;
             this.fromUuid = fromUuid;
             this.toUuid = toUuid;
             this.packageName = packageName;
-            this.dataAppName = dataAppName;
             this.appId = appId;
             this.seinfo = seinfo;
             this.targetSdkVersion = targetSdkVersion;
@@ -15120,7 +15118,7 @@
             synchronized (mInstaller) {
                 try {
                     mInstaller.moveCompleteApp(move.fromUuid, move.toUuid, move.packageName,
-                            move.dataAppName, move.appId, move.seinfo, move.targetSdkVersion,
+                            move.appId, move.seinfo, move.targetSdkVersion,
                             move.fromCodePath);
                 } catch (InstallerException e) {
                     Slog.w(TAG, "Failed to move app", e);
@@ -15128,7 +15126,8 @@
                 }
             }
 
-            codeFile = new File(Environment.getDataAppDirectory(move.toUuid), move.dataAppName);
+            final String toPathName = new File(move.fromCodePath).getName();
+            codeFile = new File(Environment.getDataAppDirectory(move.toUuid), toPathName);
             resourceFile = codeFile;
             if (DEBUG_INSTALL) Slog.d(TAG, "codeFile after move is " + codeFile);
 
@@ -15172,8 +15171,9 @@
         }
 
         private boolean cleanUp(String volumeUuid) {
+            final String toPathName = new File(move.fromCodePath).getName();
             final File codeFile = new File(Environment.getDataAppDirectory(volumeUuid),
-                    move.dataAppName);
+                    toPathName);
             Slog.d(TAG, "Cleaning up " + move.packageName + " on " + volumeUuid);
             final int[] userIds = mUserManager.getUserIds();
             synchronized (mInstallLock) {
@@ -22113,7 +22113,11 @@
             targetSdkVersion = pkg.getTargetSdkVersion();
             freezer = freezePackage(packageName, "movePackageInternal");
             installedUserIds = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
-            fromCodePath = pkg.getCodePath();
+            if (codeFile.getParentFile().getName().startsWith(RANDOM_DIR_PREFIX)) {
+                fromCodePath = codeFile.getParentFile().getAbsolutePath();
+            } else {
+                fromCodePath = codeFile.getAbsolutePath();
+            }
         }
 
         final Bundle extras = new Bundle();
@@ -22240,9 +22244,8 @@
                 }
             }).start();
 
-            final String dataAppName = codeFile.getName();
             move = new MoveInfo(moveId, currentVolumeUuid, volumeUuid, packageName,
-                    dataAppName, appId, seinfo, targetSdkVersion, fromCodePath);
+                    appId, seinfo, targetSdkVersion, fromCodePath);
         } else {
             move = null;
         }
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 2de9858..c8df5c7 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -36,6 +36,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.pm.ShortcutService.DumpFilter;
@@ -365,9 +366,12 @@
 
     /**
      * Remove all shortcuts that aren't pinned, cached nor dynamic.
+     *
+     * @return List of removed shortcuts.
      */
-    private void removeOrphans() {
+    private List<ShortcutInfo> removeOrphans() {
         ArrayList<String> removeList = null; // Lazily initialize.
+        List<ShortcutInfo> removedShortcuts = null;
 
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -376,20 +380,26 @@
 
             if (removeList == null) {
                 removeList = new ArrayList<>();
+                removedShortcuts = new ArrayList<>();
             }
             removeList.add(si.getId());
+            removedShortcuts.add(si);
         }
         if (removeList != null) {
             for (int i = removeList.size() - 1; i >= 0; i--) {
                 forceDeleteShortcutInner(removeList.get(i));
             }
         }
+
+        return removedShortcuts;
     }
 
     /**
      * Remove all dynamic shortcuts.
+     *
+     * @return List of shortcuts that actually got removed.
      */
-    public void deleteAllDynamicShortcuts(boolean ignoreInvisible) {
+    public List<ShortcutInfo> deleteAllDynamicShortcuts(boolean ignoreInvisible) {
         final long now = mShortcutUser.mService.injectCurrentTimeMillis();
 
         boolean changed = false;
@@ -404,8 +414,9 @@
             }
         }
         if (changed) {
-            removeOrphans();
+            return removeOrphans();
         }
+        return null;
     }
 
     /**
@@ -1028,7 +1039,8 @@
         s.verifyStates();
 
         // This will send a notification to the launcher, and also save .
-        s.packageShortcutsChanged(getPackageName(), getPackageUserId());
+        // TODO: List changed and removed manifest shortcuts and pass to packageShortcutsChanged()
+        s.packageShortcutsChanged(getPackageName(), getPackageUserId(), null, null);
         return true; // true means changed.
     }
 
@@ -1299,15 +1311,14 @@
      */
     public void resolveResourceStrings() {
         final ShortcutService s = mShortcutUser.mService;
-        boolean changed = false;
+
+        List<ShortcutInfo> changedShortcuts = null;
 
         Resources publisherRes = null;
         for (int i = mShortcuts.size() - 1; i >= 0; i--) {
             final ShortcutInfo si = mShortcuts.valueAt(i);
 
             if (si.hasStringResources()) {
-                changed = true;
-
                 if (publisherRes == null) {
                     publisherRes = getPackageResources();
                     if (publisherRes == null) {
@@ -1317,10 +1328,15 @@
 
                 si.resolveResourceStrings(publisherRes);
                 si.setTimestamp(s.injectCurrentTimeMillis());
+
+                if (changedShortcuts == null) {
+                    changedShortcuts = new ArrayList<>(1);
+                }
+                changedShortcuts.add(si);
             }
         }
-        if (changed) {
-            s.packageShortcutsChanged(getPackageName(), getPackageUserId());
+        if (!CollectionUtils.isEmpty(changedShortcuts)) {
+            s.packageShortcutsChanged(getPackageName(), getPackageUserId(), changedShortcuts, null);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index 3e44de9..6fd997d 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -26,7 +26,6 @@
 import android.content.pm.LauncherApps.PinItemRequest;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Pair;
@@ -36,6 +35,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Handles {@link android.content.pm.ShortcutManager#requestPinShortcut} related tasks.
  */
@@ -452,6 +454,8 @@
         final String launcherPackage = request.launcherPackage;
         final String shortcutId = original.getId();
 
+        List<ShortcutInfo> changedShortcuts = null;
+
         synchronized (mLock) {
             if (!(mService.isUserUnlockedL(appUserId)
                     && mService.isUserUnlockedL(request.launcherUserId))) {
@@ -506,8 +510,13 @@
                 Slog.d(TAG, "Pinning " + shortcutId);
             }
 
+
             launcher.addPinnedShortcut(appPackageName, appUserId, shortcutId,
                     /*forPinRequest=*/ true);
+            if (changedShortcuts == null) {
+                changedShortcuts = new ArrayList<>(1);
+            }
+            changedShortcuts.add(original);
 
             if (current == null) {
                 if (DEBUG) {
@@ -520,7 +529,7 @@
         }
 
         mService.verifyStates();
-        mService.packageShortcutsChanged(appPackageName, appUserId);
+        mService.packageShortcutsChanged(appPackageName, appUserId, changedShortcuts, null);
 
         return true;
     }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 54f9f76..66f3574 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -98,6 +98,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.Preconditions;
@@ -277,6 +278,10 @@
     private final ArrayList<ShortcutChangeListener> mListeners = new ArrayList<>(1);
 
     @GuardedBy("mLock")
+    private final ArrayList<LauncherApps.ShortcutChangeCallback> mShortcutChangeCallbacks =
+            new ArrayList<>(1);
+
+    @GuardedBy("mLock")
     private long mRawLastResetTime;
 
     /**
@@ -1639,8 +1644,12 @@
      * - Sends a notification to LauncherApps
      * - Write to file
      */
-    void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
+    void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId,
+            @Nullable List<ShortcutInfo> addedOrUpdatedShortcuts,
+            @Nullable List<ShortcutInfo> removedShortcuts) {
         notifyListeners(packageName, userId);
+        notifyShortcutChangeCallbacks(packageName, userId, addedOrUpdatedShortcuts,
+                removedShortcuts);
         scheduleSaveUser(userId);
     }
 
@@ -1668,6 +1677,34 @@
         });
     }
 
+    private void notifyShortcutChangeCallbacks(@NonNull String packageName, @UserIdInt int userId,
+            @Nullable List<ShortcutInfo> addedOrUpdatedShortcuts,
+            @Nullable List<ShortcutInfo> removedShortcuts) {
+        final UserHandle user = UserHandle.of(userId);
+        injectPostToHandler(() -> {
+            try {
+                final ArrayList<LauncherApps.ShortcutChangeCallback> copy;
+                synchronized (mLock) {
+                    if (!isUserUnlockedL(userId)) {
+                        return;
+                    }
+
+                    copy = new ArrayList<>(mShortcutChangeCallbacks);
+                }
+                for (int i = copy.size() - 1; i >= 0; i--) {
+                    if (!CollectionUtils.isEmpty(addedOrUpdatedShortcuts)) {
+                        copy.get(i).onShortcutsAddedOrUpdated(packageName, addedOrUpdatedShortcuts,
+                                user);
+                    }
+                    if (!CollectionUtils.isEmpty(removedShortcuts)) {
+                        copy.get(i).onShortcutsRemoved(packageName, removedShortcuts, user);
+                    }
+                }
+            } catch (Exception ignore) {
+            }
+        });
+    }
+
     /**
      * Clean up / validate an incoming shortcut.
      * - Make sure all mandatory fields are set.
@@ -1762,6 +1799,8 @@
         final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
                 injectBinderCallingPid(), injectBinderCallingUid());
 
+        List<ShortcutInfo> removedShortcuts = null;
+
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
@@ -1787,7 +1826,7 @@
             }
 
             // First, remove all un-pinned; dynamic shortcuts
-            ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
+            removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
 
             // Then, add/update all.  We need to make sure to take over "pinned" flag.
             for (int i = 0; i < size; i++) {
@@ -1798,7 +1837,7 @@
             // Lastly, adjust the ranks.
             ps.adjustRanks();
         }
-        packageShortcutsChanged(packageName, userId);
+        packageShortcutsChanged(packageName, userId, newShortcuts, removedShortcuts);
 
         verifyStates();
 
@@ -1817,6 +1856,8 @@
         final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
                 injectBinderCallingPid(), injectBinderCallingUid());
 
+        List<ShortcutInfo> changedShortcuts = null;
+
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
@@ -1879,12 +1920,17 @@
                 if (replacingIcon || source.hasStringResources()) {
                     fixUpShortcutResourceNamesAndValues(target);
                 }
+
+                if (changedShortcuts == null) {
+                    changedShortcuts = new ArrayList<>(1);
+                }
+                changedShortcuts.add(target);
             }
 
             // Lastly, adjust the ranks.
             ps.adjustRanks();
         }
-        packageShortcutsChanged(packageName, userId);
+        packageShortcutsChanged(packageName, userId, changedShortcuts, null);
 
         verifyStates();
 
@@ -1903,6 +1949,8 @@
         final boolean unlimited = injectHasUnlimitedShortcutsApiCallsPermission(
                 injectBinderCallingPid(), injectBinderCallingUid());
 
+        List<ShortcutInfo> changedShortcuts = null;
+
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
@@ -1934,12 +1982,17 @@
 
                 // Add it.
                 ps.addOrReplaceDynamicShortcut(newShortcut);
+
+                if (changedShortcuts == null) {
+                    changedShortcuts = new ArrayList<>(1);
+                }
+                changedShortcuts.add(newShortcut);
             }
 
             // Lastly, adjust the ranks.
             ps.adjustRanks();
         }
-        packageShortcutsChanged(packageName, userId);
+        packageShortcutsChanged(packageName, userId, changedShortcuts, null);
 
         verifyStates();
 
@@ -1985,7 +2038,7 @@
             // Lastly, adjust the ranks.
             ps.adjustRanks();
         }
-        packageShortcutsChanged(packageName, userId);
+        packageShortcutsChanged(packageName, userId, Collections.singletonList(shortcut), null);
 
         verifyStates();
     }
@@ -2052,7 +2105,8 @@
 
                     ps.updateInvisibleShortcutForPinRequestWith(shortcut);
 
-                    packageShortcutsChanged(shortcutPackage, userId);
+                    packageShortcutsChanged(shortcutPackage, userId,
+                            Collections.singletonList(shortcut), null);
                 }
             }
 
@@ -2097,7 +2151,8 @@
             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
             ps.adjustRanks();
         }
-        packageShortcutsChanged(packageName, userId);
+        // TODO: Disabling dynamic shortcuts will removed them if not pinned. Cover all cases.
+        packageShortcutsChanged(packageName, userId, null, null);
 
         verifyStates();
     }
@@ -2107,6 +2162,8 @@
         verifyCaller(packageName, userId);
         Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
 
+        List<ShortcutInfo> changedShortcuts = null;
+
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
@@ -2121,9 +2178,18 @@
                     continue;
                 }
                 ps.enableWithId(id);
+
+                final ShortcutInfo si = ps.findShortcutById(id);
+                if (si != null) {
+                    if (changedShortcuts == null) {
+                        changedShortcuts = new ArrayList<>(1);
+                    }
+                    changedShortcuts.add(si);
+                }
             }
         }
-        packageShortcutsChanged(packageName, userId);
+
+        packageShortcutsChanged(packageName, userId, changedShortcuts, null);
 
         verifyStates();
     }
@@ -2134,6 +2200,9 @@
         verifyCaller(packageName, userId);
         Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
 
+        List<ShortcutInfo> changedShortcuts = null;
+        List<ShortcutInfo> removedShortcuts = null;
+
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
@@ -2147,13 +2216,25 @@
                 if (!ps.isShortcutExistsAndVisibleToPublisher(id)) {
                     continue;
                 }
-                ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
+                final ShortcutInfo si = ps.findShortcutById(id);
+                final boolean removed = ps.deleteDynamicWithId(id, /*ignoreInvisible=*/ true);
+                if (removed) {
+                    if (removedShortcuts == null) {
+                        removedShortcuts = new ArrayList<>(1);
+                    }
+                    removedShortcuts.add(si);
+                } else {
+                    if (changedShortcuts == null) {
+                        changedShortcuts = new ArrayList<>(1);
+                    }
+                    changedShortcuts.add(si);
+                }
             }
 
             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
             ps.adjustRanks();
         }
-        packageShortcutsChanged(packageName, userId);
+        packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
 
         verifyStates();
     }
@@ -2162,13 +2243,19 @@
     public void removeAllDynamicShortcuts(String packageName, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
 
+        List<ShortcutInfo> changedShortcuts = null;
+        List<ShortcutInfo> removedShortcuts = null;
+
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
             final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
-            ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
+
+            removedShortcuts = ps.deleteAllDynamicShortcuts(/*ignoreInvisible=*/ true);
         }
-        packageShortcutsChanged(packageName, userId);
+
+        // TODO: Pinned and cached shortcuts are not removed, add those to changedShortcuts list
+        packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
 
         verifyStates();
     }
@@ -2179,6 +2266,9 @@
         verifyCaller(packageName, userId);
         Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
 
+        List<ShortcutInfo> changedShortcuts = null;
+        List<ShortcutInfo> removedShortcuts = null;
+
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
@@ -2189,13 +2279,29 @@
 
             for (int i = shortcutIds.size() - 1; i >= 0; i--) {
                 final String id = Preconditions.checkStringNotEmpty((String) shortcutIds.get(i));
-                ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+
+                final ShortcutInfo si = ps.findShortcutById(id);
+                final boolean removed = ps.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+
+                if (si != null) {
+                    if (removed) {
+                        if (removedShortcuts == null) {
+                            removedShortcuts = new ArrayList<>(1);
+                        }
+                        removedShortcuts.add(si);
+                    } else {
+                        if (changedShortcuts == null) {
+                            changedShortcuts = new ArrayList<>(1);
+                        }
+                        changedShortcuts.add(si);
+                    }
+                }
             }
 
             // We may have removed dynamic shortcuts which may have left a gap, so adjust the ranks.
             ps.adjustRanks();
         }
-        packageShortcutsChanged(packageName, userId);
+        packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
 
         verifyStates();
     }
@@ -2787,6 +2893,8 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Objects.requireNonNull(shortcutIds, "shortcutIds");
 
+            List<ShortcutInfo> changedShortcuts = null;
+
             synchronized (mLock) {
                 throwIfUserLockedL(userId);
                 throwIfUserLockedL(launcherUserId);
@@ -2796,8 +2904,23 @@
                 launcher.attemptToRestoreIfNeededAndSave();
 
                 launcher.pinShortcuts(userId, packageName, shortcutIds, /*forPinRequest=*/ false);
+
+                final ShortcutPackage sp = getUserShortcutsLocked(userId)
+                        .getPackageShortcutsIfExists(packageName);
+                if (sp != null) {
+                    for (int i = 0; i < shortcutIds.size(); i++) {
+                        final ShortcutInfo si = sp.findShortcutById(shortcutIds.get(i));
+                        if (si != null) {
+                            if (changedShortcuts == null) {
+                                changedShortcuts = new ArrayList<>(1);
+                            }
+                            changedShortcuts.add(si);
+                        }
+                    }
+                }
             }
-            packageShortcutsChanged(packageName, userId);
+            // TODO: Include previously pinned shortcuts since they are not pinned anymore.
+            packageShortcutsChanged(packageName, userId, changedShortcuts, null);
 
             verifyStates();
         }
@@ -2832,6 +2955,9 @@
             Preconditions.checkStringNotEmpty(packageName, "packageName");
             Objects.requireNonNull(shortcutIds, "shortcutIds");
 
+            List<ShortcutInfo> changedShortcuts = null;
+            List<ShortcutInfo> removedShortcuts = null;
+
             synchronized (mLock) {
                 throwIfUserLockedL(userId);
                 throwIfUserLockedL(launcherUserId);
@@ -2853,20 +2979,36 @@
                     if (doCache) {
                         if (si.isDynamic() && si.isLongLived()) {
                             si.addFlags(ShortcutInfo.FLAG_CACHED);
+                            if (changedShortcuts == null) {
+                                changedShortcuts = new ArrayList<>(1);
+                            }
+                            changedShortcuts.add(si);
                         } else {
                             Log.w(TAG, "Only dynamic long lived shortcuts can get cached. Ignoring"
                                     + "shortcut " + si.getId());
                         }
                     } else {
+                        boolean removed = false;
                         if (si.isDynamic()) {
                             si.clearFlags(ShortcutInfo.FLAG_CACHED);
                         } else {
-                            sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+                            removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
+                        }
+                        if (removed) {
+                            if (removedShortcuts == null) {
+                                removedShortcuts = new ArrayList<>(1);
+                            }
+                            removedShortcuts.add(si);
+                        } else {
+                            if (changedShortcuts == null) {
+                                changedShortcuts = new ArrayList<>(1);
+                            }
+                            changedShortcuts.add(si);
                         }
                     }
                 }
             }
-            packageShortcutsChanged(packageName, userId);
+            packageShortcutsChanged(packageName, userId, changedShortcuts, removedShortcuts);
 
             verifyStates();
         }
@@ -2912,6 +3054,14 @@
         }
 
         @Override
+        public void addShortcutChangeCallback(
+                @NonNull LauncherApps.ShortcutChangeCallback callback) {
+            synchronized (mLock) {
+                mShortcutChangeCallbacks.add(Objects.requireNonNull(callback));
+            }
+        }
+
+        @Override
         public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
                 @NonNull String packageName, @NonNull String shortcutId, int userId) {
             Objects.requireNonNull(callingPackage, "callingPackage");
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 70a9c09..f445aa8 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -190,6 +190,7 @@
 }
 
 static inline int32_t skipIdSigHeaders(borrowed_fd fd) {
+    readBEInt32(fd);        // version
     readBytes(fd);          // verityRootHash
     readBytes(fd);          // v3Digest
     readBytes(fd);          // pkcs7SignatureBlock
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b192dbd..267c444 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -444,10 +444,13 @@
     });
 
     /**
-     *  System property whose value is either "true" or "false", indicating whether
-     *  device owner is present.
+     * System property whose value indicates whether the device is fully owned by an organization:
+     * it can be either a device owner device, or a device with an organization-owned managed
+     * profile.
+     *
+     * <p>The state is stored as a Boolean string.
      */
-    private static final String PROPERTY_DEVICE_OWNER_PRESENT = "ro.organization_owned";
+    private static final String PROPERTY_ORGANIZATION_OWNED = "ro.organization_owned";
 
     private static final int STATUS_BAR_DISABLE_MASK =
             StatusBarManager.DISABLE_EXPAND |
@@ -2550,7 +2553,7 @@
     void loadOwners() {
         synchronized (getLockObject()) {
             mOwners.load();
-            setDeviceOwnerSystemPropertyLocked();
+            setDeviceOwnershipSystemPropertyLocked();
             findOwnerComponentIfNecessaryLocked();
             migrateUserRestrictionsIfNecessaryLocked();
 
@@ -2752,34 +2755,36 @@
         }
     }
 
-    private void setDeviceOwnerSystemPropertyLocked() {
-        final boolean deviceProvisioned =
-                mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0;
-        final boolean hasDeviceOwner = mOwners.hasDeviceOwner();
-        // If the device is not provisioned and there is currently no device owner, do not set the
-        // read-only system property yet, since Device owner may still be provisioned.
-        if (!hasDeviceOwner && !deviceProvisioned) {
-            return;
-        }
-        // Still at the first stage of CryptKeeper double bounce, mOwners.hasDeviceOwner is
-        // always false at this point.
+    private void setDeviceOwnershipSystemPropertyLocked() {
+        // Still at the first stage of CryptKeeper double bounce, nothing can be learnt about
+        // the real system at this point.
         if (StorageManager.inCryptKeeperBounce()) {
             return;
         }
-
-        if (!mInjector.systemPropertiesGet(PROPERTY_DEVICE_OWNER_PRESENT, "").isEmpty()) {
-            Slog.w(LOG_TAG, "Trying to set ro.organization_owned, but it has already been set?");
-        } else {
-            final String value = Boolean.toString(hasDeviceOwner);
-            mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, value);
+        final boolean deviceProvisioned =
+                mInjector.settingsGlobalGetInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        final boolean hasDeviceOwner = mOwners.hasDeviceOwner();
+        final boolean hasOrgOwnedProfile = isOrganizationOwnedDeviceWithManagedProfile();
+        // If the device is not provisioned and there is currently no management, do not set the
+        // read-only system property yet, since device owner / org-owned profile may still be
+        // provisioned.
+        if (!hasDeviceOwner && !hasOrgOwnedProfile && !deviceProvisioned) {
+            return;
+        }
+        final String value = Boolean.toString(hasDeviceOwner || hasOrgOwnedProfile);
+        final String currentVal = mInjector.systemPropertiesGet(PROPERTY_ORGANIZATION_OWNED, null);
+        if (TextUtils.isEmpty(currentVal)) {
             Slog.i(LOG_TAG, "Set ro.organization_owned property to " + value);
+            mInjector.systemPropertiesSet(PROPERTY_ORGANIZATION_OWNED, value);
+        } else if (!value.equals(currentVal)) {
+            Slog.w(LOG_TAG, "Cannot change existing ro.organization_owned to " + value);
         }
     }
 
     private void maybeStartSecurityLogMonitorOnActivityManagerReady() {
         synchronized (getLockObject()) {
             if (mInjector.securityLogIsLoggingEnabled()) {
-                mSecurityLogMonitor.start();
+                mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
                 mInjector.runCryptoSelfTest();
                 maybePauseDeviceWideLoggingLocked();
             }
@@ -8391,7 +8396,7 @@
             mOwners.setDeviceOwner(admin, ownerName, userId);
             mOwners.writeDeviceOwner();
             updateDeviceOwnerLocked();
-            setDeviceOwnerSystemPropertyLocked();
+            setDeviceOwnershipSystemPropertyLocked();
 
             mInjector.binderWithCleanCallingIdentity(() -> {
                 // Restrict adding a managed profile when a device owner is set on the device.
@@ -9071,21 +9076,21 @@
         return getApplicationLabel(profileOwner.getPackageName(), userHandle);
     }
 
+    private @UserIdInt int getOrganizationOwnedProfileUserId() {
+        for (UserInfo ui : mUserManagerInternal.getUserInfos()) {
+            if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
+                return ui.id;
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
     @Override
     public boolean isOrganizationOwnedDeviceWithManagedProfile() {
         if (!mHasFeature) {
             return false;
         }
-
-        return mInjector.binderWithCleanCallingIdentity(() -> {
-            for (UserInfo ui : mUserManager.getUsers()) {
-                if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
-                    return true;
-                }
-            }
-
-            return false;
-        });
+        return getOrganizationOwnedProfileUserId() != UserHandle.USER_NULL;
     }
 
     @Override
@@ -12046,7 +12051,7 @@
                 synchronized (getLockObject()) {
                     // Set PROPERTY_DEVICE_OWNER_PRESENT, for the SUW case where setting the property
                     // is delayed until device is marked as provisioned.
-                    setDeviceOwnerSystemPropertyLocked();
+                    setDeviceOwnershipSystemPropertyLocked();
                 }
             } else if (mDefaultImeChanged.equals(uri)) {
                 synchronized (getLockObject()) {
@@ -13671,6 +13676,22 @@
         });
     }
 
+    private boolean canStartSecurityLogging() {
+        synchronized (getLockObject()) {
+            return isOrganizationOwnedDeviceWithManagedProfile()
+                    || areAllUsersAffiliatedWithDeviceLocked();
+        }
+    }
+
+    private @UserIdInt int getSecurityLoggingEnabledUser() {
+        synchronized (getLockObject()) {
+            if (mOwners.hasDeviceOwner()) {
+                return UserHandle.USER_ALL;
+            }
+        }
+        return getOrganizationOwnedProfileUserId();
+    }
+
     @Override
     public void setSecurityLoggingEnabled(ComponentName admin, boolean enabled) {
         if (!mHasFeature) {
@@ -13679,13 +13700,14 @@
         Objects.requireNonNull(admin);
 
         synchronized (getLockObject()) {
-            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+            getActiveAdminForCallerLocked(admin,
+                    DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
             if (enabled == mInjector.securityLogGetLoggingEnabledProperty()) {
                 return;
             }
             mInjector.securityLogSetLoggingEnabledProperty(enabled);
             if (enabled) {
-                mSecurityLogMonitor.start();
+                mSecurityLogMonitor.start(getSecurityLoggingEnabledUser());
                 maybePauseDeviceWideLoggingLocked();
             } else {
                 mSecurityLogMonitor.stop();
@@ -13707,7 +13729,8 @@
         synchronized (getLockObject()) {
             if (!isCallerWithSystemUid()) {
                 Objects.requireNonNull(admin);
-                getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+                getActiveAdminForCallerLocked(admin,
+                        DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
             }
             return mInjector.securityLogGetLoggingEnabledProperty();
         }
@@ -13731,7 +13754,10 @@
         }
 
         Objects.requireNonNull(admin);
-        ensureDeviceOwnerAndAllUsersAffiliated(admin);
+        enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
+        if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+            ensureAllUsersAffiliated();
+        }
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.RETRIEVE_PRE_REBOOT_SECURITY_LOGS)
@@ -13747,6 +13773,10 @@
         ArrayList<SecurityEvent> output = new ArrayList<SecurityEvent>();
         try {
             SecurityLog.readPreviousEvents(output);
+            int enabledUser = getSecurityLoggingEnabledUser();
+            if (enabledUser != UserHandle.USER_ALL) {
+                SecurityLog.redactEvents(output, enabledUser);
+            }
             return new ParceledListSlice<SecurityEvent>(output);
         } catch (IOException e) {
             Slog.w(LOG_TAG, "Fail to read previous events" , e);
@@ -13761,7 +13791,10 @@
         }
 
         Objects.requireNonNull(admin);
-        ensureDeviceOwnerAndAllUsersAffiliated(admin);
+        enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
+        if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+            ensureAllUsersAffiliated();
+        }
 
         if (!mInjector.securityLogGetLoggingEnabledProperty()) {
             return null;
@@ -14287,26 +14320,34 @@
     @GuardedBy("getLockObject()")
     private void maybePauseDeviceWideLoggingLocked() {
         if (!areAllUsersAffiliatedWithDeviceLocked()) {
-            Slog.i(LOG_TAG, "There are unaffiliated users, security and network logging will be "
+            Slog.i(LOG_TAG, "There are unaffiliated users, network logging will be "
                     + "paused if enabled.");
-            mSecurityLogMonitor.pause();
             if (mNetworkLogger != null) {
                 mNetworkLogger.pause();
             }
+            if (!isOrganizationOwnedDeviceWithManagedProfile()) {
+                Slog.i(LOG_TAG, "Not org-owned managed profile device, security logging will be "
+                        + "paused if enabled.");
+                mSecurityLogMonitor.pause();
+            }
         }
     }
 
     /** Resumes security and network logging (if they are enabled) if all users are affiliated */
     @GuardedBy("getLockObject()")
     private void maybeResumeDeviceWideLoggingLocked() {
-        if (areAllUsersAffiliatedWithDeviceLocked()) {
-            mInjector.binderWithCleanCallingIdentity(() -> {
+        boolean allUsersAffiliated = areAllUsersAffiliatedWithDeviceLocked();
+        boolean orgOwnedProfileDevice = isOrganizationOwnedDeviceWithManagedProfile();
+        mInjector.binderWithCleanCallingIdentity(() -> {
+            if (allUsersAffiliated || orgOwnedProfileDevice) {
                 mSecurityLogMonitor.resume();
+            }
+            if (allUsersAffiliated) {
                 if (mNetworkLogger != null) {
                     mNetworkLogger.resume();
                 }
-            });
-        }
+            }
+        });
     }
 
     /** Deletes any security and network logs that might have been collected so far */
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
index 1ab3b98..3c445ca 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/SecurityLogMonitor.java
@@ -51,15 +51,17 @@
 
     private final Lock mLock = new ReentrantLock();
 
+    private int mEnabledUser;
+
     SecurityLogMonitor(DevicePolicyManagerService service) {
         this(service, 0 /* id */);
     }
 
     @VisibleForTesting
     SecurityLogMonitor(DevicePolicyManagerService service, long id) {
-        this.mService = service;
-        this.mId = id;
-        this.mLastForceNanos = System.nanoTime();
+        mService = service;
+        mId = id;
+        mLastForceNanos = System.nanoTime();
     }
 
     private static final boolean DEBUG = false;  // STOPSHIP if true.
@@ -136,8 +138,15 @@
     @GuardedBy("mForceSemaphore")
     private long mLastForceNanos = 0;
 
-    void start() {
-        Slog.i(TAG, "Starting security logging.");
+    /**
+     * Start security logging.
+     *
+     * @param enabledUser which user logging is enabled on, or USER_ALL to enable logging for all
+     *     users on the device.
+     */
+    void start(int enabledUser) {
+        Slog.i(TAG, "Starting security logging for user " + enabledUser);
+        mEnabledUser = enabledUser;
         SecurityLog.writeEvent(SecurityLog.TAG_LOGGING_STARTED);
         mLock.lock();
         try {
@@ -286,7 +295,7 @@
                 break;
             }
         }
-
+        SecurityLog.redactEvents(newLogs, mEnabledUser);
         if (DEBUG) Slog.d(TAG, "Got " + newLogs.size() + " new events.");
     }
 
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index 859cdf2..41bc361 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -274,6 +274,10 @@
         }
         protoOutputStream.write(ConversationInfoProto.SHORTCUT_FLAGS, mShortcutFlags);
         protoOutputStream.write(ConversationInfoProto.CONVERSATION_FLAGS, mConversationFlags);
+        if (mContactPhoneNumber != null) {
+            protoOutputStream.write(ConversationInfoProto.CONTACT_PHONE_NUMBER,
+                    mContactPhoneNumber);
+        }
     }
 
     /** Reads from {@link ProtoInputStream} and constructs a {@link ConversationInfo}. */
@@ -315,6 +319,10 @@
                     builder.setConversationFlags(protoInputStream.readInt(
                             ConversationInfoProto.CONVERSATION_FLAGS));
                     break;
+                case (int) ConversationInfoProto.CONTACT_PHONE_NUMBER:
+                    builder.setContactPhoneNumber(protoInputStream.readString(
+                            ConversationInfoProto.CONTACT_PHONE_NUMBER));
+                    break;
                 default:
                     Slog.w(TAG, "Could not read undefined field: "
                             + protoInputStream.getFieldNumber());
diff --git a/services/people/java/com/android/server/people/data/ConversationStore.java b/services/people/java/com/android/server/people/data/ConversationStore.java
index 62e9da8..89c4972 100644
--- a/services/people/java/com/android/server/people/data/ConversationStore.java
+++ b/services/people/java/com/android/server/people/data/ConversationStore.java
@@ -22,7 +22,6 @@
 import android.annotation.WorkerThread;
 import android.content.LocusId;
 import android.net.Uri;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.proto.ProtoInputStream;
@@ -71,16 +70,13 @@
 
     private final ScheduledExecutorService mScheduledExecutorService;
     private final File mPackageDir;
-    private final ContactsQueryHelper mHelper;
 
     private ConversationInfosProtoDiskReadWriter mConversationInfosProtoDiskReadWriter;
 
     ConversationStore(@NonNull File packageDir,
-            @NonNull ScheduledExecutorService scheduledExecutorService,
-            @NonNull ContactsQueryHelper helper) {
+            @NonNull ScheduledExecutorService scheduledExecutorService) {
         mScheduledExecutorService = scheduledExecutorService;
         mPackageDir = packageDir;
-        mHelper = helper;
     }
 
     /**
@@ -102,7 +98,6 @@
                     return;
                 }
                 for (ConversationInfo conversationInfo : conversationsOnDisk) {
-                    conversationInfo = restoreConversationPhoneNumber(conversationInfo);
                     updateConversationsInMemory(conversationInfo);
                 }
             }
@@ -250,25 +245,6 @@
         return mConversationInfosProtoDiskReadWriter;
     }
 
-    /**
-     * Conversation's phone number is not saved on disk, so it has to be fetched.
-     */
-    @WorkerThread
-    private ConversationInfo restoreConversationPhoneNumber(
-            @NonNull ConversationInfo conversationInfo) {
-        if (conversationInfo.getContactUri() != null) {
-            if (mHelper.query(conversationInfo.getContactUri().toString())) {
-                String phoneNumber = mHelper.getPhoneNumber();
-                if (!TextUtils.isEmpty(phoneNumber)) {
-                    conversationInfo = new ConversationInfo.Builder(
-                            conversationInfo).setContactPhoneNumber(
-                            phoneNumber).build();
-                }
-            }
-        }
-        return conversationInfo;
-    }
-
     /** Reads and writes {@link ConversationInfo}s on disk. */
     private static class ConversationInfosProtoDiskReadWriter extends
             AbstractProtoDiskReadWriter<List<ConversationInfo>> {
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 27c1692..3a34c6a 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -31,7 +31,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.ShortcutQuery;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager.ShareShortcutInfo;
@@ -53,6 +55,7 @@
 import android.telecom.TelecomManager;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -61,11 +64,13 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.SmsApplication;
 import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
@@ -80,8 +85,8 @@
  */
 public class DataManager {
 
-    private static final int MY_UID = Process.myUid();
-    private static final int MY_PID = Process.myPid();
+    private static final String TAG = "DataManager";
+
     private static final long QUERY_EVENTS_MAX_AGE_MS = DateUtils.DAY_IN_MILLIS;
     private static final long USAGE_STATS_QUERY_INTERVAL_SEC = 120L;
 
@@ -102,6 +107,7 @@
 
     private ShortcutServiceInternal mShortcutServiceInternal;
     private PackageManagerInternal mPackageManagerInternal;
+    private NotificationManagerInternal mNotificationManagerInternal;
     private UserManager mUserManager;
 
     public DataManager(Context context) {
@@ -120,9 +126,10 @@
     public void initialize() {
         mShortcutServiceInternal = LocalServices.getService(ShortcutServiceInternal.class);
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
         mUserManager = mContext.getSystemService(UserManager.class);
 
-        mShortcutServiceInternal.addListener(new ShortcutServiceListener());
+        mShortcutServiceInternal.addShortcutChangeCallback(new ShortcutServiceCallback());
 
         IntentFilter shutdownIntentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
         BroadcastReceiver shutdownBroadcastReceiver = new ShutdownBroadcastReceiver();
@@ -133,8 +140,7 @@
     public void onUserUnlocked(int userId) {
         UserData userData = mUserDataArray.get(userId);
         if (userData == null) {
-            userData = new UserData(userId, mDiskReadWriterExecutor,
-                    mInjector.createContactsQueryHelper(mContext));
+            userData = new UserData(userId, mDiskReadWriterExecutor);
             mUserDataArray.put(userId, userData);
         }
         userData.setUserUnlocked();
@@ -363,7 +369,7 @@
         return mShortcutServiceInternal.getShortcuts(
                 UserHandle.USER_SYSTEM, mContext.getPackageName(),
                 /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
-                /*componentName=*/ null, queryFlags, userId, MY_PID, MY_UID);
+                /*componentName=*/ null, queryFlags, userId, Process.myPid(), Process.myUid());
     }
 
     private void forAllUnlockedUsers(Consumer<UserData> consumer) {
@@ -622,14 +628,12 @@
     }
 
     /** Listener for the shortcut data changes. */
-    private class ShortcutServiceListener implements
-            ShortcutServiceInternal.ShortcutChangeListener {
+    private class ShortcutServiceCallback implements LauncherApps.ShortcutChangeCallback {
 
         @Override
-        public void onShortcutChanged(@NonNull String packageName, int userId) {
-            BackgroundThread.getExecutor().execute(() -> {
-                List<ShortcutInfo> shortcuts = getShortcuts(packageName, userId,
-                        /*shortcutIds=*/ null);
+        public void onShortcutsAddedOrUpdated(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            mInjector.getBackgroundExecutor().execute(() -> {
                 for (ShortcutInfo shortcut : shortcuts) {
                     if (isPersonShortcut(shortcut)) {
                         addOrUpdateConversationInfo(shortcut);
@@ -637,6 +641,30 @@
                 }
             });
         }
+
+        @Override
+        public void onShortcutsRemoved(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            mInjector.getBackgroundExecutor().execute(() -> {
+                int uid = Process.INVALID_UID;
+                try {
+                    uid = mContext.getPackageManager().getPackageUidAsUser(
+                            packageName, user.getIdentifier());
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.e(TAG, "Package not found: " + packageName, e);
+                }
+                PackageData packageData = getPackage(packageName, user.getIdentifier());
+                for (ShortcutInfo shortcutInfo : shortcuts) {
+                    if (packageData != null) {
+                        packageData.deleteDataForConversation(shortcutInfo.getId());
+                    }
+                    if (uid != Process.INVALID_UID) {
+                        mNotificationManagerInternal.onConversationRemoved(
+                                shortcutInfo.getPackage(), uid, shortcutInfo.getId());
+                    }
+                }
+            });
+        }
     }
 
     /** Listener for the notifications and their settings changes. */
@@ -789,6 +817,10 @@
             return Executors.newSingleThreadScheduledExecutor();
         }
 
+        Executor getBackgroundExecutor() {
+            return BackgroundThread.getExecutor();
+        }
+
         ContactsQueryHelper createContactsQueryHelper(Context context) {
             return new ContactsQueryHelper(context);
         }
diff --git a/services/people/java/com/android/server/people/data/PackageData.java b/services/people/java/com/android/server/people/data/PackageData.java
index d47e2cc..35d245f 100644
--- a/services/people/java/com/android/server/people/data/PackageData.java
+++ b/services/people/java/com/android/server/people/data/PackageData.java
@@ -59,16 +59,14 @@
             @NonNull Predicate<String> isDefaultDialerPredicate,
             @NonNull Predicate<String> isDefaultSmsAppPredicate,
             @NonNull ScheduledExecutorService scheduledExecutorService,
-            @NonNull File perUserPeopleDataDir,
-            @NonNull ContactsQueryHelper helper) {
+            @NonNull File perUserPeopleDataDir) {
         mPackageName = packageName;
         mUserId = userId;
 
         mPackageDataDir = new File(perUserPeopleDataDir, mPackageName);
         mPackageDataDir.mkdirs();
 
-        mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService,
-                helper);
+        mConversationStore = new ConversationStore(mPackageDataDir, scheduledExecutorService);
         mEventStore = new EventStore(mPackageDataDir, scheduledExecutorService);
         mIsDefaultDialerPredicate = isDefaultDialerPredicate;
         mIsDefaultSmsAppPredicate = isDefaultSmsAppPredicate;
@@ -83,8 +81,7 @@
             @NonNull Predicate<String> isDefaultDialerPredicate,
             @NonNull Predicate<String> isDefaultSmsAppPredicate,
             @NonNull ScheduledExecutorService scheduledExecutorService,
-            @NonNull File perUserPeopleDataDir,
-            @NonNull ContactsQueryHelper helper) {
+            @NonNull File perUserPeopleDataDir) {
         Map<String, PackageData> results = new ArrayMap<>();
         File[] packageDirs = perUserPeopleDataDir.listFiles(File::isDirectory);
         if (packageDirs == null) {
@@ -93,7 +90,7 @@
         for (File packageDir : packageDirs) {
             PackageData packageData = new PackageData(packageDir.getName(), userId,
                     isDefaultDialerPredicate, isDefaultSmsAppPredicate, scheduledExecutorService,
-                    perUserPeopleDataDir, helper);
+                    perUserPeopleDataDir);
             packageData.loadFromDisk();
             results.put(packageDir.getName(), packageData);
         }
diff --git a/services/people/java/com/android/server/people/data/UserData.java b/services/people/java/com/android/server/people/data/UserData.java
index d3cecce..0f8b91b 100644
--- a/services/people/java/com/android/server/people/data/UserData.java
+++ b/services/people/java/com/android/server/people/data/UserData.java
@@ -37,8 +37,6 @@
 
     private final ScheduledExecutorService mScheduledExecutorService;
 
-    private final ContactsQueryHelper mHelper;
-
     private boolean mIsUnlocked;
 
     private Map<String, PackageData> mPackageDataMap = new ArrayMap<>();
@@ -49,12 +47,10 @@
     @Nullable
     private String mDefaultSmsApp;
 
-    UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService,
-            ContactsQueryHelper helper) {
+    UserData(@UserIdInt int userId, @NonNull ScheduledExecutorService scheduledExecutorService) {
         mUserId = userId;
         mPerUserPeopleDataDir = new File(Environment.getDataSystemCeDirectory(mUserId), "people");
         mScheduledExecutorService = scheduledExecutorService;
-        mHelper = helper;
     }
 
     @UserIdInt int getUserId() {
@@ -74,7 +70,7 @@
         // data from disk.
         mPerUserPeopleDataDir.mkdirs();
         mPackageDataMap.putAll(PackageData.packagesDataFromDisk(mUserId, this::isDefaultDialer,
-                this::isDefaultSmsApp, mScheduledExecutorService, mPerUserPeopleDataDir, mHelper));
+                this::isDefaultSmsApp, mScheduledExecutorService, mPerUserPeopleDataDir));
     }
 
     void setUserStopped() {
@@ -131,7 +127,7 @@
 
     private PackageData createPackageData(String packageName) {
         return new PackageData(packageName, mUserId, this::isDefaultDialer, this::isDefaultSmsApp,
-                mScheduledExecutorService, mPerUserPeopleDataDir, mHelper);
+                mScheduledExecutorService, mPerUserPeopleDataDir);
     }
 
     private boolean isDefaultDialer(String packageName) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 1c8b00f..30bb38a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -111,6 +111,29 @@
                 any());
     }
 
+    @Test
+    public void testRegisterAuthenticator_callsInitConfiguredStrength() throws Exception {
+
+        final String[] config = {
+                "0:2:15", // ID0:Fingerprint:Strong
+                "1:4:255", // ID1:Iris:Weak
+                "2:8:4095", // ID2:Face:Convenience
+        };
+
+        when(mInjector.getConfiguration(any())).thenReturn(config);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final int fingerprintStrength = 15;
+        final int irisStrength = 255;
+        final int faceStrength = 4095;
+
+        verify(mFingerprintService).initConfiguredStrength(eq(fingerprintStrength));
+        verify(mIrisService).initConfiguredStrength(eq(irisStrength));
+        verify(mFaceService).initConfiguredStrength(eq(faceStrength));
+    }
+
 
     // TODO(b/141025588): Check that an exception is thrown when the userId != callingUserId
     @Test
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 dbf2f14..ac818ea 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3952,13 +3952,8 @@
     }
 
     public void testIsOrganizationOwnedDevice() throws Exception {
-        setupProfileOwner();
         // Set up the user manager to return correct user info
-        UserInfo managedProfileUserInfo = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
-                "managed profile",
-                UserInfo.FLAG_MANAGED_PROFILE);
-        when(getServices().userManager.getUsers())
-                .thenReturn(Arrays.asList(managedProfileUserInfo));
+        addManagedProfile(admin1, DpmMockContext.CALLER_UID, admin1);
 
         // Any caller should be able to call this method.
         assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
@@ -5909,8 +5904,6 @@
     }
 
     private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
-        when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
-                .thenReturn(UserHandle.SYSTEM);
         final long ident = mServiceContext.binder.clearCallingIdentity();
         mServiceContext.binder.callingUid = UserHandle.getUid(userId, DpmMockContext.SYSTEM_UID);
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 37d4081..01f1a3e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -265,6 +265,9 @@
                             .toArray();
                 }
         );
+        when(userManagerInternal.getUserInfos()).thenReturn(
+                mUserInfos.toArray(new UserInfo[mUserInfos.size()]));
+
         when(accountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
 
         // Create a data directory.
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
index 0f05212..8dcf21f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/SecurityEventTest.java
@@ -1,31 +1,43 @@
 package com.android.server.devicepolicy;
 
 import static android.app.admin.SecurityLog.TAG_ADB_SHELL_CMD;
+import static android.app.admin.SecurityLog.TAG_APP_PROCESS_START;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED;
+import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED;
+import static android.app.admin.SecurityLog.TAG_KEY_DESTRUCTION;
+import static android.app.admin.SecurityLog.TAG_KEY_GENERATED;
+import static android.app.admin.SecurityLog.TAG_KEY_IMPORT;
+import static android.app.admin.SecurityLog.TAG_KEY_INTEGRITY_VIOLATION;
+import static android.app.admin.SecurityLog.TAG_MEDIA_MOUNT;
+import static android.app.admin.SecurityLog.TAG_MEDIA_UNMOUNT;
 
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.os.Parcel;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.EventLog.Event;
 
-import java.io.IOException;
+import junit.framework.AssertionFailedError;
+
 import java.util.ArrayList;
 import java.util.List;
 
-@SmallTest
 public class SecurityEventTest extends DpmTestBase {
-    private static long ID = 549;
-    private static String DATA = "adb shell some_command";
 
-    public void testSecurityEventId() {
-        SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
-        assertEquals(ID, event.getId());
+    public void testSecurityEventId() throws Exception {
+        SecurityEvent event = createEvent(() -> {
+            EventLog.writeEvent(TAG_ADB_SHELL_CMD, 0);
+        }, TAG_ADB_SHELL_CMD);
         event.setId(20);
         assertEquals(20, event.getId());
     }
 
-    public void testSecurityEventParceling() {
+    public void testSecurityEventParceling() throws Exception {
         // GIVEN an event.
-        SecurityEvent event = buildSecurityEvents(1 /* generate a single event */, ID).get(0);
+        SecurityEvent event = createEvent(() -> {
+            EventLog.writeEvent(TAG_ADB_SHELL_CMD, "test");
+        }, TAG_ADB_SHELL_CMD);
         // WHEN parceling the event.
         Parcel p = Parcel.obtain();
         p.writeParcelable(event, 0);
@@ -39,23 +51,104 @@
         assertEquals(event.getId(), unparceledEvent.getId());
     }
 
-    private List<SecurityEvent> buildSecurityEvents(int numEvents, long id) {
-        // Write an event to the EventLog.
-        for (int i = 0; i < numEvents; i++) {
-            EventLog.writeEvent(TAG_ADB_SHELL_CMD, DATA + "_" + i);
+    public void testSecurityEventRedaction() throws Exception {
+        SecurityEvent event;
+
+        // TAG_ADB_SHELL_CMD will has the command redacted
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_ADB_SHELL_CMD, "command");
+        }, TAG_ADB_SHELL_CMD);
+        assertFalse(TextUtils.isEmpty((String) event.getData()));
+
+        // TAG_MEDIA_MOUNT will have the volume label redacted (second data)
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_MEDIA_MOUNT, new Object[] {"path", "label"});
+        }, TAG_MEDIA_MOUNT);
+        assertFalse(TextUtils.isEmpty(event.getStringData(1)));
+        assertTrue(TextUtils.isEmpty(event.redact(0).getStringData(1)));
+
+        // TAG_MEDIA_UNMOUNT will have the volume label redacted (second data)
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_MEDIA_UNMOUNT, new Object[] {"path", "label"});
+        }, TAG_MEDIA_UNMOUNT);
+        assertFalse(TextUtils.isEmpty(event.getStringData(1)));
+        assertTrue(TextUtils.isEmpty(event.redact(0).getStringData(1)));
+
+        // TAG_APP_PROCESS_START will be fully redacted if user does not match
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_APP_PROCESS_START, new Object[] {"process", 12345L,
+                    UserHandle.getUid(10, 123), 456, "seinfo", "hash"});
+        }, TAG_APP_PROCESS_START);
+        assertNotNull(event.redact(10));
+        assertNull(event.redact(11));
+
+        // TAG_CERT_AUTHORITY_INSTALLED will be fully redacted if user does not match
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_CERT_AUTHORITY_INSTALLED, new Object[] {1, "subject", 10});
+        }, TAG_CERT_AUTHORITY_INSTALLED);
+        assertNotNull(event.redact(10));
+        assertNull(event.redact(11));
+
+        // TAG_CERT_AUTHORITY_REMOVED will be fully redacted if user does not match
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_CERT_AUTHORITY_REMOVED, new Object[] {1, "subject", 20});
+        }, TAG_CERT_AUTHORITY_REMOVED);
+        assertNotNull(event.redact(20));
+        assertNull(event.redact(0));
+
+        // TAG_KEY_GENERATED will be fully redacted if user does not match
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_KEY_GENERATED,
+                    new Object[] {1, "alias", UserHandle.getUid(0, 123)});
+        }, TAG_KEY_GENERATED);
+        assertNotNull(event.redact(0));
+        assertNull(event.redact(10));
+
+        // TAG_KEY_IMPORT will be fully redacted if user does not match
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_KEY_IMPORT,
+                    new Object[] {1, "alias", UserHandle.getUid(1, 123)});
+        }, TAG_KEY_IMPORT);
+        assertNotNull(event.redact(1));
+        assertNull(event.redact(10));
+
+        // TAG_KEY_DESTRUCTION will be fully redacted if user does not match
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_KEY_DESTRUCTION,
+                    new Object[] {1, "alias", UserHandle.getUid(2, 123)});
+        }, TAG_KEY_DESTRUCTION);
+        assertNotNull(event.redact(2));
+        assertNull(event.redact(10));
+
+        // TAG_KEY_INTEGRITY_VIOLATION will be fully redacted if user does not match
+        event = createEvent(() -> {
+            EventLog.writeEvent(TAG_KEY_INTEGRITY_VIOLATION,
+                    new Object[] {"alias", UserHandle.getUid(2, 123)});
+        }, TAG_KEY_INTEGRITY_VIOLATION);
+        assertNotNull(event.redact(2));
+        assertNull(event.redact(10));
+
+    }
+
+    /**
+     * Creates an Event object. Only the native code has the serialization and deserialization logic
+     * so need to actually emit a real log in order to generate the object.
+     */
+    private SecurityEvent createEvent(Runnable generator, int expectedTag) throws Exception {
+        Long markerData = System.currentTimeMillis();
+        EventLog.writeEvent(expectedTag, markerData);
+        generator.run();
+
+        List<Event> events = new ArrayList<>();
+        // Give the message some time to show up in the log
+        Thread.sleep(20);
+        EventLog.readEvents(new int[] {expectedTag}, events);
+
+        for (int i = 0; i < events.size() - 1; i++) {
+            if (markerData.equals(events.get(i).getData())) {
+                return new SecurityEvent(0, events.get(i + 1).getBytes());
+            }
         }
-        List<EventLog.Event> events = new ArrayList<>();
-        try {
-            EventLog.readEvents(new int[]{TAG_ADB_SHELL_CMD}, events);
-        } catch (IOException e) {
-            fail("Reading a test event from storage failed: " + e);
-        }
-        assertTrue("Unexpected number of events read from the log.", events.size() >= numEvents);
-        // Read events generated by test, from the end of the log.
-        List<SecurityEvent> securityEvents = new ArrayList<>();
-        for (int i = events.size() - numEvents; i < events.size(); i++) {
-          securityEvents.add(new SecurityEvent(id++, events.get(i).getBytes()));
-        }
-        return securityEvents;
+        throw new AssertionFailedError("Unable to locate marker event");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
index 03b5e38..d138700 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationStoreTest.java
@@ -21,7 +21,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.LocusId;
 import android.content.pm.ShortcutInfo;
@@ -63,7 +62,6 @@
     private static final String PHONE_NUMBER_3 = "+9234567890";
 
     private MockScheduledExecutorService mMockScheduledExecutorService;
-    private TestContactQueryHelper mTestContactQueryHelper;
     private ConversationStore mConversationStore;
     private File mFile;
 
@@ -71,7 +69,6 @@
     public void setUp() {
         Context ctx = InstrumentationRegistry.getContext();
         mFile = new File(ctx.getCacheDir(), "testdir");
-        mTestContactQueryHelper = new TestContactQueryHelper(ctx);
         resetConversationStore();
     }
 
@@ -207,9 +204,6 @@
         mConversationStore.deleteConversation(SHORTCUT_ID_3);
         mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
 
-        mTestContactQueryHelper.setQueryResult(true, true);
-        mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2);
-
         resetConversationStore();
         ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
         ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
@@ -240,9 +234,6 @@
         mConversationStore.addOrUpdate(in2);
         mMockScheduledExecutorService.fastForwardTime(DateUtils.MINUTE_IN_MILLIS);
 
-        mTestContactQueryHelper.setQueryResult(true);
-        mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER);
-
         resetConversationStore();
         ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
         ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
@@ -256,10 +247,6 @@
         mConversationStore.addOrUpdate(in3);
         mMockScheduledExecutorService.fastForwardTime(3L * DateUtils.MINUTE_IN_MILLIS);
 
-        mTestContactQueryHelper.reset();
-        mTestContactQueryHelper.setQueryResult(true, true, true);
-        mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2, PHONE_NUMBER_3);
-
         resetConversationStore();
         out1 = mConversationStore.getConversation(SHORTCUT_ID);
         out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
@@ -290,9 +277,6 @@
         // loadConversationFromDisk gets called each time we call #resetConversationStore().
         assertEquals(2, mMockScheduledExecutorService.getExecutes().size());
 
-        mTestContactQueryHelper.setQueryResult(true, true);
-        mTestContactQueryHelper.setPhoneNumberResult(PHONE_NUMBER, PHONE_NUMBER_2);
-
         resetConversationStore();
         ConversationInfo out1 = mConversationStore.getConversation(SHORTCUT_ID);
         ConversationInfo out2 = mConversationStore.getConversation(SHORTCUT_ID_2);
@@ -303,8 +287,7 @@
     private void resetConversationStore() {
         mFile.mkdir();
         mMockScheduledExecutorService = new MockScheduledExecutorService();
-        mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService,
-                mTestContactQueryHelper);
+        mConversationStore = new ConversationStore(mFile, mMockScheduledExecutorService);
         mConversationStore.loadConversationsFromDisk();
     }
 
@@ -326,54 +309,4 @@
                 .setBubbled(true)
                 .build();
     }
-
-    private static class TestContactQueryHelper extends ContactsQueryHelper {
-
-        private int mQueryCalls;
-        private boolean[] mQueryResult;
-
-        private int mPhoneNumberCalls;
-        private String[] mPhoneNumberResult;
-
-        TestContactQueryHelper(Context context) {
-            super(context);
-
-            mQueryCalls = 0;
-            mPhoneNumberCalls = 0;
-        }
-
-        private void setQueryResult(boolean... values) {
-            mQueryResult = values;
-        }
-
-        private void setPhoneNumberResult(String... values) {
-            mPhoneNumberResult = values;
-        }
-
-        private void reset() {
-            mQueryCalls = 0;
-            mQueryResult = null;
-            mPhoneNumberCalls = 0;
-            mPhoneNumberResult = null;
-        }
-
-        @Override
-        boolean query(String contactUri) {
-            if (mQueryResult != null && mQueryCalls < mQueryResult.length) {
-                return mQueryResult[mQueryCalls++];
-            }
-            mQueryCalls++;
-            return false;
-        }
-
-        @Override
-        @Nullable
-        String getPhoneNumber() {
-            if (mPhoneNumberResult != null && mPhoneNumberCalls < mPhoneNumberResult.length) {
-                return mPhoneNumberResult[mPhoneNumberCalls++];
-            }
-            mPhoneNumberCalls++;
-            return null;
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index b54317b..f0b7d20 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -52,6 +52,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.LauncherApps.ShortcutChangeCallback;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutServiceInternal;
@@ -72,6 +74,7 @@
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.content.PackageMonitor;
 import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 
 import org.junit.After;
@@ -79,6 +82,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -87,6 +92,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
@@ -102,6 +108,7 @@
     private static final String TEST_PKG_NAME = "pkg";
     private static final String TEST_CLASS_NAME = "class";
     private static final String TEST_SHORTCUT_ID = "sc";
+    private static final int TEST_PKG_UID = 35;
     private static final String CONTACT_URI = "content://com.android.contacts/contacts/lookup/123";
     private static final String PHONE_NUMBER = "+1234567890";
     private static final String NOTIFICATION_CHANNEL_ID = "test : sc";
@@ -111,7 +118,9 @@
     @Mock private ShortcutServiceInternal mShortcutServiceInternal;
     @Mock private UsageStatsManagerInternal mUsageStatsManagerInternal;
     @Mock private PackageManagerInternal mPackageManagerInternal;
+    @Mock private NotificationManagerInternal mNotificationManagerInternal;
     @Mock private UserManager mUserManager;
+    @Mock private PackageManager mPackageManager;
     @Mock private TelephonyManager mTelephonyManager;
     @Mock private TelecomManager mTelecomManager;
     @Mock private ContentResolver mContentResolver;
@@ -121,13 +130,16 @@
     @Mock private StatusBarNotification mStatusBarNotification;
     @Mock private Notification mNotification;
 
+    @Captor private ArgumentCaptor<ShortcutChangeCallback> mShortcutChangeCallbackCaptor;
+
     private NotificationChannel mNotificationChannel;
     private DataManager mDataManager;
     private CancellationSignal mCancellationSignal;
+    private ShortcutChangeCallback mShortcutChangeCallback;
     private TestInjector mInjector;
 
     @Before
-    public void setUp() {
+    public void setUp() throws PackageManager.NameNotFoundException {
         MockitoAnnotations.initMocks(this);
 
         addLocalServiceMock(ShortcutServiceInternal.class, mShortcutServiceInternal);
@@ -143,8 +155,12 @@
             return null;
         }).when(mPackageManagerInternal).forEachInstalledPackage(any(Consumer.class), anyInt());
 
+        addLocalServiceMock(NotificationManagerInternal.class, mNotificationManagerInternal);
+
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
         when(mContext.getPackageName()).thenReturn("android");
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
 
         Context originalContext = getInstrumentation().getTargetContext();
         when(mContext.getApplicationInfo()).thenReturn(originalContext.getApplicationInfo());
@@ -175,7 +191,8 @@
         when(mUserManager.getEnabledProfiles(USER_ID_SECONDARY))
                 .thenReturn(Collections.singletonList(buildUserInfo(USER_ID_SECONDARY)));
 
-        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        when(mPackageManager.getPackageUidAsUser(TEST_PKG_NAME, USER_ID_PRIMARY))
+                .thenReturn(TEST_PKG_UID);
 
         when(mStatusBarNotification.getNotification()).thenReturn(mNotification);
         when(mStatusBarNotification.getPackageName()).thenReturn(TEST_PKG_NAME);
@@ -192,6 +209,10 @@
         mInjector = new TestInjector();
         mDataManager = new DataManager(mContext, mInjector);
         mDataManager.initialize();
+
+        verify(mShortcutServiceInternal).addShortcutChangeCallback(
+                mShortcutChangeCallbackCaptor.capture());
+        mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
     }
 
     @After
@@ -474,6 +495,43 @@
     }
 
     @Test
+    public void testShortcutAddedOrUpdated() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
+                buildPerson());
+        mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
+                Collections.singletonList(shortcut), UserHandle.of(USER_ID_PRIMARY));
+
+        List<ConversationInfo> conversations = getConversationsInPrimary();
+
+        assertEquals(1, conversations.size());
+        assertEquals(TEST_SHORTCUT_ID, conversations.get(0).getShortcutId());
+    }
+
+    @Test
+    public void testShortcutDeleted() {
+        mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+
+        ShortcutInfo shortcut1 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc1",
+                buildPerson());
+        ShortcutInfo shortcut2 = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, "sc2",
+                buildPerson());
+        mShortcutChangeCallback.onShortcutsAddedOrUpdated(TEST_PKG_NAME,
+                Arrays.asList(shortcut1, shortcut2), UserHandle.of(USER_ID_PRIMARY));
+        mShortcutChangeCallback.onShortcutsRemoved(TEST_PKG_NAME,
+                Collections.singletonList(shortcut1), UserHandle.of(USER_ID_PRIMARY));
+
+        List<ConversationInfo> conversations = getConversationsInPrimary();
+
+        assertEquals(1, conversations.size());
+        assertEquals("sc2", conversations.get(0).getShortcutId());
+
+        verify(mNotificationManagerInternal)
+                .onConversationRemoved(TEST_PKG_NAME, TEST_PKG_UID, "sc1");
+    }
+
+    @Test
     public void testCallLogContentObserver() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
         mDataManager.onUserUnlocked(USER_ID_SECONDARY);
@@ -765,6 +823,11 @@
         }
 
         @Override
+        Executor getBackgroundExecutor() {
+            return Runnable::run;
+        }
+
+        @Override
         ContactsQueryHelper createContactsQueryHelper(Context context) {
             return mContactsQueryHelper;
         }
diff --git a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
index e52cdf5..8191d17 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/PackageDataTest.java
@@ -61,7 +61,7 @@
         testDir.mkdir();
         mPackageData = new PackageData(
                 PACKAGE_NAME, USER_ID, pkg -> mIsDefaultDialer, pkg -> mIsDefaultSmsApp,
-                new MockScheduledExecutorService(), testDir, new ContactsQueryHelper(ctx));
+                new MockScheduledExecutorService(), testDir);
         ConversationInfo conversationInfo = new ConversationInfo.Builder()
                 .setShortcutId(SHORTCUT_ID)
                 .setLocusId(LOCUS_ID)
diff --git a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
index 418067f..7934d33 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/UsageStatsQueryHelperTest.java
@@ -79,10 +79,9 @@
         Context ctx = InstrumentationRegistry.getContext();
         File testDir = new File(ctx.getCacheDir(), "testdir");
         ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
-        ContactsQueryHelper helper = new ContactsQueryHelper(ctx);
 
         mPackageData = new TestPackageData(PKG_NAME, USER_ID_PRIMARY, pkg -> false, pkg -> false,
-                scheduledExecutorService, testDir, helper);
+                scheduledExecutorService, testDir);
         mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
                 .setShortcutId(SHORTCUT_ID)
                 .setLocusId(LOCUS_ID_1)
@@ -221,9 +220,8 @@
         private ConversationInfo mConversationInfo;
 
         TestConversationStore(File packageDir,
-                ScheduledExecutorService scheduledExecutorService,
-                ContactsQueryHelper helper) {
-            super(packageDir, scheduledExecutorService, helper);
+                ScheduledExecutorService scheduledExecutorService) {
+            super(packageDir, scheduledExecutorService);
         }
 
         @Override
@@ -241,12 +239,10 @@
         TestPackageData(@NonNull String packageName, @UserIdInt int userId,
                 @NonNull Predicate<String> isDefaultDialerPredicate,
                 @NonNull Predicate<String> isDefaultSmsAppPredicate,
-                @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir,
-                @NonNull ContactsQueryHelper helper) {
+                @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir) {
             super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate,
-                    scheduledExecutorService, rootDir, helper);
-            mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService,
-                    helper);
+                    scheduledExecutorService, rootDir);
+            mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService);
             mEventStore = new TestEventStore(rootDir, scheduledExecutorService);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 118c540..bec37e9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -812,7 +812,7 @@
                 "android", 0, mUserManager.getPrimaryUser().getUserHandle())
                 .getSystemService(Context.USER_SERVICE);
 
-        List<UserHandle> profiles = um.getUserProfiles(false);
+        List<UserHandle> profiles = um.getAllProfiles();
         assertThat(profiles.size()).isEqualTo(2);
         assertThat(profiles.get(0).equals(userProfile.getUserHandle())
                 || profiles.get(1).equals(userProfile.getUserHandle())).isTrue();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ccce043..d0283f7 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3246,7 +3246,7 @@
                 new BufferedInputStream(new ByteArrayInputStream(upgradeXml.getBytes())),
                 false,
                 UserHandle.USER_ALL);
-        verify(mSnoozeHelper, times(1)).readXml(any(XmlPullParser.class));
+        verify(mSnoozeHelper, times(1)).readXml(any(XmlPullParser.class), anyLong());
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 1dd0b1a..816e8e5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -22,6 +22,7 @@
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
@@ -96,10 +97,33 @@
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(xml_string.getBytes())), null);
-        mSnoozeHelper.readXml(parser);
-        assertTrue("Should read the notification time from xml and it should be more than zero",
-                0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
-                        0, "pkg", "key").doubleValue());
+        mSnoozeHelper.readXml(parser, 1);
+        assertEquals((long) Long.MAX_VALUE, (long) mSnoozeHelper
+                .getSnoozeTimeForUnpostedNotification(0, "pkg", "key"));
+        verify(mAm, never()).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
+    }
+
+    @Test
+    public void testWriteXML_afterReading_noNPE()
+            throws XmlPullParserException, IOException {
+        final String max_time_str = Long.toString(Long.MAX_VALUE);
+        final String xml_string = "<snoozed-notifications>"
+                + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+                + "pkg=\"pkg\" key=\"key\" time=\"" + max_time_str + "\"/>"
+                + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+                + "pkg=\"pkg\" key=\"key2\" time=\"" + max_time_str + "\"/>"
+                + "</snoozed-notifications>";
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml_string.getBytes())), null);
+        mSnoozeHelper.readXml(parser, 1);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        XmlSerializer serializer = new FastXmlSerializer();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mSnoozeHelper.writeXml(serializer);
+        serializer.endDocument();
+        serializer.flush();
     }
 
     @Test
@@ -115,7 +139,7 @@
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(xml_string.getBytes())), null);
-        mSnoozeHelper.readXml(parser);
+        mSnoozeHelper.readXml(parser, 1);
         assertEquals("Should read the notification context from xml and it should be `uri",
                 "uri", mSnoozeHelper.getSnoozeContextForUnpostedNotification(
                         0, "pkg", "key"));
@@ -137,7 +161,7 @@
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(baos.toByteArray())), "utf-8");
-        mSnoozeHelper.readXml(parser);
+        mSnoozeHelper.readXml(parser, 1);
         assertTrue("Should read the notification time from xml and it should be more than zero",
                 0 < mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
                         0, "pkg", r.getKey()).doubleValue());
@@ -161,7 +185,7 @@
         XmlPullParser parser = Xml.newPullParser();
         parser.setInput(new BufferedInputStream(
                 new ByteArrayInputStream(baos.toByteArray())), "utf-8");
-        mSnoozeHelper.readXml(parser);
+        mSnoozeHelper.readXml(parser, 2);
         int systemUser = UserHandle.SYSTEM.getIdentifier();
         assertTrue("Should see a past time returned",
                 System.currentTimeMillis() >  mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
@@ -195,6 +219,30 @@
     }
 
     @Test
+    public void testScheduleRepostsForPersistedNotifications() throws Exception {
+        final String xml_string = "<snoozed-notifications>"
+                + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+                + "pkg=\"pkg\" key=\"key\" time=\"" + 10 + "\"/>"
+                + "<notification version=\"1\" user-id=\"0\" notification=\"notification\" "
+                + "pkg=\"pkg\" key=\"key2\" time=\"" + 15+ "\"/>"
+                + "</snoozed-notifications>";
+        XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml_string.getBytes())), null);
+        mSnoozeHelper.readXml(parser, 4);
+
+        mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq((long) 10), captor.capture());
+        assertEquals("key", captor.getValue().getIntent().getStringExtra(EXTRA_KEY));
+
+        ArgumentCaptor<PendingIntent> captor2 = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), eq((long) 15), captor2.capture());
+        assertEquals("key2", captor2.getValue().getIntent().getStringExtra(EXTRA_KEY));
+    }
+
+    @Test
     public void testSnoozeForTime() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r, 1000);
@@ -414,6 +462,23 @@
     }
 
     @Test
+    public void testGetSnoozedGroupNotifications_nonGrouped() throws Exception {
+        IntArray profileIds = new IntArray();
+        profileIds.add(UserHandle.USER_CURRENT);
+        when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
+        NotificationRecord r = getNotificationRecord("pkg", 1, "tag",
+                UserHandle.CURRENT, "group", true);
+        NotificationRecord r2 = getNotificationRecord("pkg", 2, "tag",
+                UserHandle.CURRENT, null, true);
+        mSnoozeHelper.snooze(r, 1000);
+        mSnoozeHelper.snooze(r2, 1000);
+
+        assertEquals(1,
+                mSnoozeHelper.getNotifications("pkg", "group", UserHandle.USER_CURRENT).size());
+        // and no NPE
+    }
+
+    @Test
     public void testGetSnoozedNotificationByKey() throws Exception {
         IntArray profileIds = new IntArray();
         profileIds.add(UserHandle.USER_CURRENT);
diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py
index 9661927..6a5b0e1 100755
--- a/tools/hiddenapi/merge_csv.py
+++ b/tools/hiddenapi/merge_csv.py
@@ -14,26 +14,56 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """
-Merge mutliple CSV files, possibly with different columns, writing to stdout.
+Merge multiple CSV files, possibly with different columns.
 """
 
+import argparse
 import csv
-import sys
+import io
 
-csv_readers = [
-    csv.DictReader(open(csv_file, 'r'), delimiter=',', quotechar='|')
-    for csv_file in sys.argv[1:]
-]
+from zipfile import ZipFile
 
-# Build union of all columns from source files:
+args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.')
+args_parser.add_argument('--header', help='Comma separated field names; '
+                                          'if missing determines the header from input files.')
+args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.')
+args_parser.add_argument('--output', help='Output file for merged CSV.',
+                         default='-', type=argparse.FileType('w'))
+args_parser.add_argument('files', nargs=argparse.REMAINDER)
+args = args_parser.parse_args()
+
+
+def dict_reader(input):
+    return csv.DictReader(input, delimiter=',', quotechar='|')
+
+
+if args.zip_input and len(args.files) > 0:
+    raise ValueError('Expecting either a single ZIP with CSV files'
+                     ' or a list of CSV files as input; not both.')
+
+csv_readers = []
+if len(args.files) > 0:
+    for file in args.files:
+        csv_readers.append(dict_reader(open(file, 'r')))
+elif args.zip_input:
+    with ZipFile(args.zip_input) as zip:
+        for entry in zip.namelist():
+            if entry.endswith('.uau'):
+                csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
+
 headers = set()
-for reader in csv_readers:
-    headers = headers.union(reader.fieldnames)
+if args.header:
+    fieldnames = args.header.split(',')
+else:
+    # Build union of all columns from source files:
+    for reader in csv_readers:
+        headers = headers.union(reader.fieldnames)
+    fieldnames = sorted(headers)
 
 # Concatenate all files to output:
-out = csv.DictWriter(sys.stdout, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
-                     dialect='unix', fieldnames=sorted(headers))
-out.writeheader()
+writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL,
+                        dialect='unix', fieldnames=fieldnames)
+writer.writeheader()
 for reader in csv_readers:
     for row in reader:
-        out.writerow(row)
+        writer.writerow(row)