Merge "Remove WindowlessWindowLayout"
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 53c56e7..9ec74e5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1033,10 +1033,10 @@
             }
             boolean needFileMigration = false;
             long nowElapsed = sElapsedRealtimeClock.millis();
-            for (File file : files) {
-                final AtomicFile aFile = createJobFile(file);
-                try (FileInputStream fis = aFile.openRead()) {
-                    synchronized (mLock) {
+            synchronized (mLock) {
+                for (File file : files) {
+                    final AtomicFile aFile = createJobFile(file);
+                    try (FileInputStream fis = aFile.openRead()) {
                         jobs = readJobMapImpl(fis, rtcGood, nowElapsed);
                         if (jobs != null) {
                             for (int i = 0; i < jobs.size(); i++) {
@@ -1054,33 +1054,35 @@
                                 }
                             }
                         }
+                    } catch (FileNotFoundException e) {
+                        // mJobFileDirectory.listFiles() gave us this file...why can't we find it???
+                        Slog.e(TAG, "Could not find jobs file: " + file.getName());
+                    } catch (XmlPullParserException | IOException e) {
+                        Slog.wtf(TAG, "Error in " + file.getName(), e);
+                    } catch (Exception e) {
+                        // Crashing at this point would result in a boot loop, so live with a
+                        // generic Exception for system stability's sake.
+                        Slog.wtf(TAG, "Unexpected exception", e);
                     }
-                } catch (FileNotFoundException e) {
-                    // mJobFileDirectory.listFiles() gave us this file...why can't we find it???
-                    Slog.e(TAG, "Could not find jobs file: " + file.getName());
-                } catch (XmlPullParserException | IOException e) {
-                    Slog.wtf(TAG, "Error in " + file.getName(), e);
-                } catch (Exception e) {
-                    // Crashing at this point would result in a boot loop, so live with a general
-                    // Exception for system stability's sake.
-                    Slog.wtf(TAG, "Unexpected exception", e);
-                }
-                if (mUseSplitFiles) {
-                    if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
-                        // We're supposed to be using the split file architecture, but we still have
-                        // the old job file around. Fully migrate and remove the old file.
+                    if (mUseSplitFiles) {
+                        if (!file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+                            // We're supposed to be using the split file architecture,
+                            // but we still have
+                            // the old job file around. Fully migrate and remove the old file.
+                            needFileMigration = true;
+                        }
+                    } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
+                        // We're supposed to be using the legacy single file architecture,
+                        // but we still have some job split files around. Fully migrate
+                        // and remove the split files.
                         needFileMigration = true;
                     }
-                } else if (file.getName().startsWith(JOB_FILE_SPLIT_PREFIX)) {
-                    // We're supposed to be using the legacy single file architecture, but we still
-                    // have some job split files around. Fully migrate and remove the split files.
-                    needFileMigration = true;
                 }
-            }
-            if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
-                mPersistInfo.countAllJobsLoaded = numJobs;
-                mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
-                mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+                if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
+                    mPersistInfo.countAllJobsLoaded = numJobs;
+                    mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
+                    mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+                }
             }
             Slog.i(TAG, "Read " + numJobs + " jobs");
             if (needFileMigration) {
diff --git a/core/api/current.txt b/core/api/current.txt
index dd3d6eb..2590e9e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -31658,7 +31658,8 @@
     method public static void scaleM(float[], int, float, float, float);
     method public static void setIdentityM(float[], int);
     method public static void setLookAtM(float[], int, float, float, float, float, float, float, float, float, float);
-    method public static void setRotateEulerM(float[], int, float, float, float);
+    method @Deprecated public static void setRotateEulerM(float[], int, float, float, float);
+    method public static void setRotateEulerM2(@NonNull float[], int, float, float, float);
     method public static void setRotateM(float[], int, float, float, float, float);
     method public static void translateM(float[], int, float[], int, float, float, float);
     method public static void translateM(float[], int, float, float, float);
@@ -36397,6 +36398,7 @@
     field public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI = "android.settings.PROCESS_WIFI_EASY_CONNECT_URI";
     field public static final String ACTION_QUICK_ACCESS_WALLET_SETTINGS = "android.settings.QUICK_ACCESS_WALLET_SETTINGS";
     field public static final String ACTION_QUICK_LAUNCH_SETTINGS = "android.settings.QUICK_LAUNCH_SETTINGS";
+    field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS";
     field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
     field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA";
     field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM";
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 447b113..3216bd1 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -171,6 +171,7 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void setHfpEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void setHfpSamplingRate(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void setHfpVolume(int);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK) public void setLeAudioSuspended(boolean);
     method public void setStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
     field public static final int FLAG_FROM_KEY = 4096; // 0x1000
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1254560..4f50415d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10981,6 +10981,7 @@
     field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS";
     field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
     field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
+    field public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG = "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
     field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
     field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
     field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 32d88b2..c0239e8 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -9085,6 +9085,25 @@
                 state, sourceSpec, targetSpec, viewIds, uiTranslationSpec);
     }
 
+    /**
+     * If set, any activity launch in the same task will be overridden to the locale of activity
+     * that started the task.
+     *
+     * <p>Currently, Android supports per app languages, and system apps are able to start
+     * activities of another package on the same task, which may cause users to set different
+     * languages in different apps and display two different languages in one app.</p>
+     *
+     * <p>The <a href="https://developer.android.com/guide/topics/large-screens/activity-embedding">
+     * activity embedding feature</a> will align the locale with root activity automatically, but
+     * it doesn't land on the phone yet. If activity embedding land on the phone in the future,
+     * please consider adapting activity embedding directly.</p>
+     *
+     * @hide
+     */
+    public void enableTaskLocaleOverride() {
+        ActivityClient.getInstance().enableTaskLocaleOverride(mToken);
+    }
+
     class HostCallbacks extends FragmentHostCallback<Activity> {
         public HostCallbacks() {
             super(Activity.this /*activity*/);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 558dae5..aa868a7 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -563,6 +563,14 @@
         }
     }
 
+    void enableTaskLocaleOverride(IBinder token) {
+        try {
+            getActivityClientController().enableTaskLocaleOverride(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Shows or hides a Camera app compat toggle for stretched issues with the requested state.
      *
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index ecea46a..03646c6 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -171,4 +171,10 @@
      */
     oneway void requestCompatCameraControl(in IBinder token, boolean showControl,
             boolean transformationApplied, in ICompatCameraControlCallback callback);
+
+    /**
+     * If set, any activity launch in the same task will be overridden to the locale of activity
+     * that started the task.
+     */
+    void enableTaskLocaleOverride(in IBinder token);
 }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 7f0f44b..51662af 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -50,6 +50,8 @@
             "updateCrossProfileIntentFiltersOnOTA";
     private static final String ATTR_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL =
             "crossProfileIntentFilterAccessControl";
+    private static final String ATTR_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY =
+            "crossProfileIntentResolutionStrategy";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
@@ -59,7 +61,8 @@
             INDEX_INHERIT_DEVICE_POLICY,
             INDEX_USE_PARENTS_CONTACTS,
             INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA,
-            INDEX_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL
+            INDEX_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL,
+            INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -71,6 +74,7 @@
     private static final int INDEX_USE_PARENTS_CONTACTS = 4;
     private static final int INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA = 5;
     private static final int INDEX_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL = 6;
+    private static final int INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY = 7;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -219,6 +223,39 @@
     public static final int CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM_ADD_ONLY = 20;
 
     /**
+     * Possible values for cross profile intent resolution strategy.
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_"}, value = {
+            CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT,
+            CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CrossProfileIntentResolutionStrategy {
+    }
+
+    /**
+     * Signifies to use {@link DefaultCrossProfileResolver} strategy, which
+     * check if it needs to skip the initiating profile, resolves intent in target profile.
+     * {@link DefaultCrossProfileResolver} also filters the {@link ResolveInfo} after intent
+     * resolution based on their domain approval level
+     *
+     * @hide
+     */
+    public static final int CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT = 0;
+
+    /**
+     * Signifies that there is no need to filter {@link ResolveInfo} after cross profile intent
+     * resolution across. This strategy is for profile acting transparent to end-user and resolves
+     * all allowed intent without giving any profile priority.
+     *
+     * @hide
+     */
+    public static final int CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING = 1;
+
+
+    /**
      * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
      * default properties, which it uses for any property not subsequently set.
      * @hide
@@ -255,6 +292,7 @@
             setUpdateCrossProfileIntentFiltersOnOTA(orig.getUpdateCrossProfileIntentFiltersOnOTA());
             setCrossProfileIntentFilterAccessControl(
                     orig.getCrossProfileIntentFilterAccessControl());
+            setCrossProfileIntentResolutionStrategy(orig.getCrossProfileIntentResolutionStrategy());
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
@@ -466,6 +504,36 @@
     }
     private @CrossProfileIntentFilterAccessControlLevel int mCrossProfileIntentFilterAccessControl;
 
+    /**
+     * Returns the user's {@link CrossProfileIntentResolutionStrategy}. If not explicitly
+     * configured, default value is {@link #CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT}.
+     * @return user's {@link CrossProfileIntentResolutionStrategy}.
+     *
+     * @hide
+     */
+    public @CrossProfileIntentResolutionStrategy int getCrossProfileIntentResolutionStrategy() {
+        if (isPresent(INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY)) {
+            return mCrossProfileIntentResolutionStrategy;
+        }
+        if (mDefaultProperties != null) {
+            return mDefaultProperties.mCrossProfileIntentResolutionStrategy;
+        }
+        throw new SecurityException("You don't have permission to query "
+                + "crossProfileIntentResolutionStrategy");
+    }
+    /**
+     * Sets {@link CrossProfileIntentResolutionStrategy} for the user.
+     * @param val resolution strategy for user
+     * @hide
+     */
+    public void setCrossProfileIntentResolutionStrategy(
+            @CrossProfileIntentResolutionStrategy int val) {
+        this.mCrossProfileIntentResolutionStrategy = val;
+        setPresent(INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY);
+    }
+    private @CrossProfileIntentResolutionStrategy int mCrossProfileIntentResolutionStrategy;
+
+
     @Override
     public String toString() {
         // Please print in increasing order of PropertyIndex.
@@ -480,6 +548,8 @@
                 + getUpdateCrossProfileIntentFiltersOnOTA()
                 + ", mCrossProfileIntentFilterAccessControl="
                 + getCrossProfileIntentFilterAccessControl()
+                + ", mCrossProfileIntentResolutionStrategy="
+                + getCrossProfileIntentResolutionStrategy()
                 + "}";
     }
 
@@ -500,6 +570,8 @@
                 + getUpdateCrossProfileIntentFiltersOnOTA());
         pw.println(prefix + "    mCrossProfileIntentFilterAccessControl="
                 + getCrossProfileIntentFilterAccessControl());
+        pw.println(prefix + "    mCrossProfileIntentResolutionStrategy="
+                + getCrossProfileIntentResolutionStrategy());
     }
 
     /**
@@ -554,6 +626,9 @@
                 case ATTR_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL:
                     setCrossProfileIntentFilterAccessControl(parser.getAttributeInt(i));
                     break;
+                case ATTR_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY:
+                    setCrossProfileIntentResolutionStrategy(parser.getAttributeInt(i));
+                    break;
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -597,6 +672,10 @@
             serializer.attributeInt(null, ATTR_CROSS_PROFILE_INTENT_FILTER_ACCESS_CONTROL,
                     mCrossProfileIntentFilterAccessControl);
         }
+        if (isPresent(INDEX_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY)) {
+            serializer.attributeInt(null, ATTR_CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY,
+                    mCrossProfileIntentResolutionStrategy);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -610,6 +689,7 @@
         dest.writeBoolean(mUseParentsContacts);
         dest.writeBoolean(mUpdateCrossProfileIntentFiltersOnOTA);
         dest.writeInt(mCrossProfileIntentFilterAccessControl);
+        dest.writeInt(mCrossProfileIntentResolutionStrategy);
     }
 
     /**
@@ -627,6 +707,7 @@
         mUseParentsContacts = source.readBoolean();
         mUpdateCrossProfileIntentFiltersOnOTA = source.readBoolean();
         mCrossProfileIntentFilterAccessControl = source.readInt();
+        mCrossProfileIntentResolutionStrategy = source.readInt();
     }
 
     @Override
@@ -660,6 +741,8 @@
         private @CrossProfileIntentFilterAccessControlLevel int
                 mCrossProfileIntentFilterAccessControl =
                 CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_ALL;
+        private @CrossProfileIntentResolutionStrategy int mCrossProfileIntentResolutionStrategy =
+                CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -704,6 +787,13 @@
             return this;
         }
 
+        /** Sets the value for {@link #mCrossProfileIntentResolutionStrategy} */
+        public Builder setCrossProfileIntentResolutionStrategy(@CrossProfileIntentResolutionStrategy
+                int crossProfileIntentResolutionStrategy) {
+            mCrossProfileIntentResolutionStrategy = crossProfileIntentResolutionStrategy;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
@@ -713,7 +803,8 @@
                     mInheritDevicePolicy,
                     mUseParentsContacts,
                     mUpdateCrossProfileIntentFiltersOnOTA,
-                    mCrossProfileIntentFilterAccessControl);
+                    mCrossProfileIntentFilterAccessControl,
+                    mCrossProfileIntentResolutionStrategy);
         }
     } // end Builder
 
@@ -724,7 +815,8 @@
             @ShowInSettings int showInSettings,
             @InheritDevicePolicy int inheritDevicePolicy,
             boolean useParentsContacts, boolean updateCrossProfileIntentFiltersOnOTA,
-            @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl) {
+            @CrossProfileIntentFilterAccessControlLevel int crossProfileIntentFilterAccessControl,
+            @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy) {
 
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
@@ -734,5 +826,6 @@
         setUseParentsContacts(useParentsContacts);
         setUpdateCrossProfileIntentFiltersOnOTA(updateCrossProfileIntentFiltersOnOTA);
         setCrossProfileIntentFilterAccessControl(crossProfileIntentFilterAccessControl);
+        setCrossProfileIntentResolutionStrategy(crossProfileIntentResolutionStrategy);
     }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2bdd360..190b738 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1079,6 +1079,17 @@
             "android.settings.APP_LOCALE_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of regional preferences
+     * <p>
+     * Input: Nothing
+     * <p>
+     * Output: Nothing.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS =
+            "android.settings.REGIONAL_PREFERENCES_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of lockscreen.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
@@ -2501,6 +2512,7 @@
      *
      * @hide
      */
+    @SystemApi
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_SHOW_RESTRICTED_SETTING_DIALOG =
             "android.settings.SHOW_RESTRICTED_SETTING_DIALOG";
@@ -18088,6 +18100,14 @@
              * @hide
              */
             public static final int EARLY_UPDATES_STATUS_ABORTED = 4;
+
+            /**
+             * Whether dynamic color theming (e.g. Material You) is enabled for apps which support
+             * it.
+             *
+             * @hide
+             */
+            public static final String DYNAMIC_COLOR_THEME_ENABLED = "dynamic_color_theme_enabled";
         }
     }
 
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 1ff7ae6..5bc9847 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -4234,6 +4234,13 @@
         public float relativeY;
 
         /**
+         * Whether these coordinate data were generated by resampling.
+         *
+         * @hide
+         */
+        public boolean isResampled;
+
+        /**
          * Clears the contents of this object.
          * Resets all axes to zero.
          */
@@ -4251,6 +4258,7 @@
             orientation = 0;
             relativeX = 0;
             relativeY = 0;
+            isResampled = false;
         }
 
         /**
@@ -4283,6 +4291,7 @@
             orientation = other.orientation;
             relativeX = other.relativeX;
             relativeY = other.relativeY;
+            isResampled = other.isResampled;
         }
 
         /**
diff --git a/core/java/android/view/MotionPredictor.java b/core/java/android/view/MotionPredictor.java
new file mode 100644
index 0000000..3e58a31
--- /dev/null
+++ b/core/java/android/view/MotionPredictor.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2023 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.view;
+
+import android.annotation.NonNull;
+import android.content.Context;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Calculates motion predictions.
+ *
+ * Add motions here to get predicted events!
+ * @hide
+ */
+// Acts as a pass-through to the native MotionPredictor object.
+// Do not store any state in this Java layer, or add any business logic here. All of the
+// implementation details should go into the native MotionPredictor.
+// The context / resource access must be here rather than in native layer due to the lack of the
+// corresponding native API surface.
+public final class MotionPredictor {
+
+    private static class RegistryHolder {
+        public static final NativeAllocationRegistry REGISTRY =
+                NativeAllocationRegistry.createMalloced(
+                        MotionPredictor.class.getClassLoader(),
+                        nativeGetNativeMotionPredictorFinalizer());
+    }
+
+    // Pointer to the native object.
+    private final long mPtr;
+    private final Context mContext;
+
+    /**
+     * Create a new MotionPredictor for the provided {@link Context}.
+     * @param context The context for the predictions
+     */
+    public MotionPredictor(@NonNull Context context) {
+        mContext = context;
+        final int offsetNanos = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_motionPredictionOffsetNanos);
+        mPtr = nativeInitialize(offsetNanos);
+        RegistryHolder.REGISTRY.registerNativeAllocation(this, mPtr);
+    }
+
+    /**
+     * Record a movement so that in the future, a prediction for the current gesture can be
+     * generated. Ensure to add all motions from the gesture of interest to generate the correct
+     * prediction.
+     * @param event The received event
+     */
+    public void record(@NonNull MotionEvent event) {
+        nativeRecord(mPtr, event);
+    }
+
+    /**
+     * Get predicted events for all gestures that have been provided to the 'record' function.
+     * If events from multiple devices were sent to 'record', this will produce a separate
+     * {@link MotionEvent} for each device id. The returned list may be empty if no predictions for
+     * any of the added events are available.
+     * Predictions may not reach the requested timestamp if the confidence in the prediction results
+     * is low.
+     *
+     * @param predictionTimeNanos The time that the prediction should target, in the
+     * {@link android.os.SystemClock#uptimeMillis} time base, but in nanoseconds.
+     *
+     * @return the list of predicted motion events, for each device id. Ensure to check the
+     * historical data in addition to the latest ({@link MotionEvent#getX getX()},
+     * {@link MotionEvent#getY getY()}) coordinates for smoothest prediction curves. Empty list is
+     * returned if predictions are not supported, or not possible for the current set of gestures.
+     */
+    @NonNull
+    public List<MotionEvent> predict(long predictionTimeNanos) {
+        return Arrays.asList(nativePredict(mPtr, predictionTimeNanos));
+    }
+
+    /**
+     * Check whether this device supports motion predictions for the given source type.
+     *
+     * @param deviceId The input device id
+     * @param source The source of input events
+     * @return True if the current device supports predictions, false otherwise.
+     */
+    public boolean isPredictionAvailable(int deviceId, int source) {
+        // Device-specific override
+        if (!mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableMotionPrediction)) {
+            return false;
+        }
+        return nativeIsPredictionAvailable(mPtr, deviceId, source);
+    }
+
+    private static native long nativeInitialize(int offsetNanos);
+    private static native void nativeRecord(long nativePtr, MotionEvent event);
+    private static native MotionEvent[] nativePredict(long nativePtr, long predictionTimeNanos);
+    private static native boolean nativeIsPredictionAvailable(long nativePtr, int deviceId,
+            int source);
+    private static native long nativeGetNativeMotionPredictorFinalizer();
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2dbd8e4..3502c34 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -227,6 +227,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.OptionalInt;
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -10865,6 +10866,12 @@
         public View mSource;
         public long mLastEventTimeMillis;
         /**
+         * Keep track of action that caused the event.
+         * This is empty if there's no performing actions for pending events.
+         * This is zero if there're multiple events performed for pending events.
+         */
+        @NonNull public OptionalInt mAction = OptionalInt.empty();
+        /**
          * Override for {@link AccessibilityEvent#originStackTrace} to provide the stack trace
          * of the original {@link #runOrPost} call instead of one for sending the delayed event
          * from a looper.
@@ -10887,6 +10894,7 @@
                 AccessibilityEvent event = AccessibilityEvent.obtain();
                 event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
                 event.setContentChangeTypes(mChangeTypes);
+                if (mAction.isPresent()) event.setAction(mAction.getAsInt());
                 if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
                 source.sendAccessibilityEventUnchecked(event);
             } else {
@@ -10895,6 +10903,7 @@
             // In any case reset to initial state.
             source.resetSubtreeAccessibilityStateChanged();
             mChangeTypes = 0;
+            mAction = OptionalInt.empty();
             if (AccessibilityEvent.DEBUG_ORIGIN) mOrigin = null;
         }
 
@@ -10924,10 +10933,27 @@
                 }
                 mSource = (predecessor != null) ? predecessor : source;
                 mChangeTypes |= changeType;
+
+                final int performingAction = mAccessibilityManager.getPerformingAction();
+                if (performingAction != 0) {
+                    if (mAction.isEmpty()) {
+                        mAction = OptionalInt.of(performingAction);
+                    } else if (mAction.getAsInt() != performingAction) {
+                        // Multiple actions are performed for pending events. We cannot decide one
+                        // action here.
+                        // We're doing best effort to set action value, and it's fine to set
+                        // no action this case.
+                        mAction = OptionalInt.of(0);
+                    }
+                }
+
                 return;
             }
             mSource = source;
             mChangeTypes = changeType;
+            if (mAccessibilityManager.getPerformingAction() != 0) {
+                mAction = OptionalInt.of(mAccessibilityManager.getPerformingAction());
+            }
             if (AccessibilityEvent.DEBUG_ORIGIN) {
                 mOrigin = Thread.currentThread().getStackTrace();
             }
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 9abbba9..8e335e8 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1275,6 +1275,15 @@
     }
 
     /**
+     * Get the id of {@link AccessibilityNodeInfo.AccessibilityAction} currently being performed.
+     *
+     * @hide
+     */
+    public int getPerformingAction() {
+        return mPerformingAction;
+    }
+
+    /**
      * Registers a {@link HighTextContrastChangeListener} for changes in
      * the global high text contrast state of the system.
      *
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index c9ddf92..203d79a 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -71,20 +71,42 @@
      *
      * This is needed in case we need to launch a placeholder Activity to split below a transparent
      * always-expand Activity.
+     *
+     * This should not be used with {@link #mPairedActivityToken}.
      */
     @Nullable
     private final IBinder mPairedPrimaryFragmentToken;
 
+    /**
+     * The Activity token to place the new TaskFragment on top of.
+     * When it is set, the new TaskFragment will be positioned right above the target Activity.
+     * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+     *
+     * This is needed in case we need to place an Activity into TaskFragment to launch placeholder
+     * below a transparent always-expand Activity, or when there is another Intent being started in
+     * a TaskFragment above.
+     *
+     * This should not be used with {@link #mPairedPrimaryFragmentToken}.
+     */
+    @Nullable
+    private final IBinder mPairedActivityToken;
+
     private TaskFragmentCreationParams(
             @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
-            @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
+            @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken,
+            @Nullable IBinder pairedActivityToken) {
+        if (pairedPrimaryFragmentToken != null && pairedActivityToken != null) {
+            throw new IllegalArgumentException("pairedPrimaryFragmentToken and"
+                    + " pairedActivityToken should not be set at the same time.");
+        }
         mOrganizer = organizer;
         mFragmentToken = fragmentToken;
         mOwnerToken = ownerToken;
         mInitialBounds.set(initialBounds);
         mWindowingMode = windowingMode;
         mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
+        mPairedActivityToken = pairedActivityToken;
     }
 
     @NonNull
@@ -121,6 +143,15 @@
         return mPairedPrimaryFragmentToken;
     }
 
+    /**
+     * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+     * @hide
+     */
+    @Nullable
+    public IBinder getPairedActivityToken() {
+        return mPairedActivityToken;
+    }
+
     private TaskFragmentCreationParams(Parcel in) {
         mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
@@ -128,6 +159,7 @@
         mInitialBounds.readFromParcel(in);
         mWindowingMode = in.readInt();
         mPairedPrimaryFragmentToken = in.readStrongBinder();
+        mPairedActivityToken = in.readStrongBinder();
     }
 
     /** @hide */
@@ -139,6 +171,7 @@
         mInitialBounds.writeToParcel(dest, flags);
         dest.writeInt(mWindowingMode);
         dest.writeStrongBinder(mPairedPrimaryFragmentToken);
+        dest.writeStrongBinder(mPairedActivityToken);
     }
 
     @NonNull
@@ -164,6 +197,7 @@
                 + " initialBounds=" + mInitialBounds
                 + " windowingMode=" + mWindowingMode
                 + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+                + " pairedActivityToken=" + mPairedActivityToken
                 + "}";
     }
 
@@ -194,6 +228,9 @@
         @Nullable
         private IBinder mPairedPrimaryFragmentToken;
 
+        @Nullable
+        private IBinder mPairedActivityToken;
+
         public Builder(@NonNull TaskFragmentOrganizerToken organizer,
                 @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
             mOrganizer = organizer;
@@ -224,6 +261,8 @@
          * This is needed in case we need to launch a placeholder Activity to split below a
          * transparent always-expand Activity.
          *
+         * This should not be used with {@link #setPairedActivityToken}.
+         *
          * TODO(b/232476698): remove the hide with adding CTS for this in next release.
          * @hide
          */
@@ -233,11 +272,32 @@
             return this;
         }
 
+        /**
+         * Sets the Activity token to place the new TaskFragment on top of.
+         * When it is set, the new TaskFragment will be positioned right above the target Activity.
+         * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+         *
+         * This is needed in case we need to place an Activity into TaskFragment to launch
+         * placeholder below a transparent always-expand Activity, or when there is another Intent
+         * being started in a TaskFragment above.
+         *
+         * This should not be used with {@link #setPairedPrimaryFragmentToken}.
+         *
+         * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+         * @hide
+         */
+        @NonNull
+        public Builder setPairedActivityToken(@Nullable IBinder activityToken) {
+            mPairedActivityToken = activityToken;
+            return this;
+        }
+
         /** Constructs the options to create TaskFragment with. */
         @NonNull
         public TaskFragmentCreationParams build() {
             return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
-                    mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
+                    mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken,
+                    mPairedActivityToken);
         }
     }
 }
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 4f97d21..bf55255 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -21,6 +21,7 @@
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
+import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
 import static com.android.internal.util.ArrayUtils.convertToLongArray;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -151,11 +152,13 @@
                             Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
                             "1" /* Value to enable */, "0" /* Value to disable */,
                             R.string.color_correction_feature_name));
-            featuresMap.put(ONE_HANDED_COMPONENT_NAME,
-                    new ToggleableFrameworkFeatureInfo(
-                            Settings.Secure.ONE_HANDED_MODE_ACTIVATED,
-                            "1" /* Value to enable */, "0" /* Value to disable */,
-                            R.string.one_handed_mode_feature_name));
+            if (SUPPORT_ONE_HANDED_MODE) {
+                featuresMap.put(ONE_HANDED_COMPONENT_NAME,
+                        new ToggleableFrameworkFeatureInfo(
+                                Settings.Secure.ONE_HANDED_MODE_ACTIVATED,
+                                "1" /* Value to enable */, "0" /* Value to disable */,
+                                R.string.one_handed_mode_feature_name));
+            }
             featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME,
                     new ToggleableFrameworkFeatureInfo(
                             Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
index b5455f2..a47a97c 100644
--- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
+++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java
@@ -26,6 +26,7 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
 import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
 import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
+import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityShortcutInfo;
@@ -210,6 +211,7 @@
                         context.getString(R.string.accessibility_magnification_chooser_text),
                         context.getDrawable(R.drawable.ic_accessibility_magnification),
                         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+        targets.add(magnification);
 
         final ToggleAllowListingFeatureTarget daltonizer =
                 new ToggleAllowListingFeatureTarget(context,
@@ -220,6 +222,7 @@
                         context.getString(R.string.color_correction_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_color_correction),
                         Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
+        targets.add(daltonizer);
 
         final ToggleAllowListingFeatureTarget colorInversion =
                 new ToggleAllowListingFeatureTarget(context,
@@ -230,16 +233,20 @@
                         context.getString(R.string.color_inversion_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_color_inversion),
                         Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+        targets.add(colorInversion);
 
-        final ToggleAllowListingFeatureTarget oneHandedMode =
-                new ToggleAllowListingFeatureTarget(context,
-                        shortcutType,
-                        isShortcutContained(context, shortcutType,
-                                ONE_HANDED_COMPONENT_NAME.flattenToString()),
-                        ONE_HANDED_COMPONENT_NAME.flattenToString(),
-                        context.getString(R.string.one_handed_mode_feature_name),
-                        context.getDrawable(R.drawable.ic_accessibility_one_handed),
-                        Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
+        if (SUPPORT_ONE_HANDED_MODE) {
+            final ToggleAllowListingFeatureTarget oneHandedMode =
+                    new ToggleAllowListingFeatureTarget(context,
+                            shortcutType,
+                            isShortcutContained(context, shortcutType,
+                                    ONE_HANDED_COMPONENT_NAME.flattenToString()),
+                            ONE_HANDED_COMPONENT_NAME.flattenToString(),
+                            context.getString(R.string.one_handed_mode_feature_name),
+                            context.getDrawable(R.drawable.ic_accessibility_one_handed),
+                            Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
+            targets.add(oneHandedMode);
+        }
 
         final ToggleAllowListingFeatureTarget reduceBrightColors =
                 new ToggleAllowListingFeatureTarget(context,
@@ -250,6 +257,7 @@
                         context.getString(R.string.reduce_bright_colors_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
                         Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
+        targets.add(reduceBrightColors);
 
         final InvisibleToggleAllowListingFeatureTarget hearingAids =
                 new InvisibleToggleAllowListingFeatureTarget(context,
@@ -260,11 +268,6 @@
                         context.getString(R.string.hearing_aids_feature_name),
                         context.getDrawable(R.drawable.ic_accessibility_hearing_aid),
                         /* key= */ null);
-        targets.add(magnification);
-        targets.add(daltonizer);
-        targets.add(colorInversion);
-        targets.add(oneHandedMode);
-        targets.add(reduceBrightColors);
         targets.add(hearingAids);
 
         return targets;
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 6870d09..af205d2 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -31,6 +31,8 @@
             SystemProperties.getInt("ro.factorytest", 0);
     public static final String CONTROL_PRIVAPP_PERMISSIONS =
             SystemProperties.get("ro.control_privapp_permissions");
+    public static final boolean SUPPORT_ONE_HANDED_MODE =
+            SystemProperties.getBoolean("ro.support_one_handed_mode", /* def= */ false);
 
     // ------ ro.hdmi.* -------- //
     /**
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index a43f0b3..21f1d6d 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -129,6 +129,7 @@
                 "android_view_KeyCharacterMap.cpp",
                 "android_view_KeyEvent.cpp",
                 "android_view_MotionEvent.cpp",
+                "android_view_MotionPredictor.cpp",
                 "android_view_PointerIcon.cpp",
                 "android_view_Surface.cpp",
                 "android_view_SurfaceControl.cpp",
@@ -283,6 +284,7 @@
                 "libhwui",
                 "libmediandk",
                 "libpermission",
+                "libPlatformProperties",
                 "libsensor",
                 "libinput",
                 "libcamera_client",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6ceffde..578cf24 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -187,6 +187,7 @@
 extern int register_android_view_KeyCharacterMap(JNIEnv *env);
 extern int register_android_view_KeyEvent(JNIEnv* env);
 extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_MotionPredictor(JNIEnv* env);
 extern int register_android_view_PointerIcon(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
 extern int register_android_view_VerifiedKeyEvent(JNIEnv* env);
@@ -1643,6 +1644,7 @@
         REG_JNI(register_android_view_InputQueue),
         REG_JNI(register_android_view_KeyEvent),
         REG_JNI(register_android_view_MotionEvent),
+        REG_JNI(register_android_view_MotionPredictor),
         REG_JNI(register_android_view_PointerIcon),
         REG_JNI(register_android_view_VelocityTracker),
         REG_JNI(register_android_view_VerifiedKeyEvent),
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 17cfbfc..3b409d4 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -541,87 +541,6 @@
             indices.mData, indexCount);
 }
 
-#define I(_i, _j) ((_j)+ 4*(_i))
-
-static
-void multiplyMM(float* r, const float* lhs, const float* rhs)
-{
-    for (int i=0 ; i<4 ; i++) {
-        const float rhs_i0 = rhs[ I(i,0) ];
-        float ri0 = lhs[ I(0,0) ] * rhs_i0;
-        float ri1 = lhs[ I(0,1) ] * rhs_i0;
-        float ri2 = lhs[ I(0,2) ] * rhs_i0;
-        float ri3 = lhs[ I(0,3) ] * rhs_i0;
-        for (int j=1 ; j<4 ; j++) {
-            const float rhs_ij = rhs[ I(i,j) ];
-            ri0 += lhs[ I(j,0) ] * rhs_ij;
-            ri1 += lhs[ I(j,1) ] * rhs_ij;
-            ri2 += lhs[ I(j,2) ] * rhs_ij;
-            ri3 += lhs[ I(j,3) ] * rhs_ij;
-        }
-        r[ I(i,0) ] = ri0;
-        r[ I(i,1) ] = ri1;
-        r[ I(i,2) ] = ri2;
-        r[ I(i,3) ] = ri3;
-    }
-}
-
-static
-void util_multiplyMM(JNIEnv *env, jclass clazz,
-    jfloatArray result_ref, jint resultOffset,
-    jfloatArray lhs_ref, jint lhsOffset,
-    jfloatArray rhs_ref, jint rhsOffset) {
-
-    FloatArrayHelper resultMat(env, result_ref, resultOffset, 16);
-    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
-    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 16);
-
-    bool checkOK = resultMat.check() && lhs.check() && rhs.check();
-
-    if ( !checkOK ) {
-        return;
-    }
-
-    resultMat.bind();
-    lhs.bind();
-    rhs.bind();
-
-    multiplyMM(resultMat.mData, lhs.mData, rhs.mData);
-
-    resultMat.commitChanges();
-}
-
-static
-void multiplyMV(float* r, const float* lhs, const float* rhs)
-{
-    mx4transform(rhs[0], rhs[1], rhs[2], rhs[3], lhs, r);
-}
-
-static
-void util_multiplyMV(JNIEnv *env, jclass clazz,
-    jfloatArray result_ref, jint resultOffset,
-    jfloatArray lhs_ref, jint lhsOffset,
-    jfloatArray rhs_ref, jint rhsOffset) {
-
-    FloatArrayHelper resultV(env, result_ref, resultOffset, 4);
-    FloatArrayHelper lhs(env, lhs_ref, lhsOffset, 16);
-    FloatArrayHelper rhs(env, rhs_ref, rhsOffset, 4);
-
-    bool checkOK = resultV.check() && lhs.check() && rhs.check();
-
-    if ( !checkOK ) {
-        return;
-    }
-
-    resultV.bind();
-    lhs.bind();
-    rhs.bind();
-
-    multiplyMV(resultV.mData, lhs.mData, rhs.mData);
-
-    resultV.commitChanges();
-}
-
 // ---------------------------------------------------------------------------
 
 // The internal format is no longer the same as pixel format, per Table 2 in
@@ -1014,11 +933,6 @@
  * JNI registration
  */
 
-static const JNINativeMethod gMatrixMethods[] = {
-    { "multiplyMM", "([FI[FI[FI)V", (void*)util_multiplyMM },
-    { "multiplyMV", "([FI[FI[FI)V", (void*)util_multiplyMV },
-};
-
 static const JNINativeMethod gVisibilityMethods[] = {
     { "computeBoundingSphere", "([FII[FI)V", (void*)util_computeBoundingSphere },
     { "frustumCullSpheres", "([FI[FII[III)I", (void*)util_frustumCullSpheres },
@@ -1051,7 +965,6 @@
 } ClassRegistrationInfo;
 
 static const ClassRegistrationInfo gClasses[] = {
-    {"android/opengl/Matrix", gMatrixMethods, NELEM(gMatrixMethods)},
     {"android/opengl/Visibility", gVisibilityMethods, NELEM(gVisibilityMethods)},
     {"android/opengl/GLUtils", gUtilsMethods, NELEM(gUtilsMethods)},
     {"android/opengl/ETC1", gEtc1Methods, NELEM(gEtc1Methods)},
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 7002d9b..7d379e5 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -97,7 +97,6 @@
     return env->NewLocalRef(inputDeviceObj.get());
 }
 
-
 int register_android_view_InputDevice(JNIEnv* env)
 {
     gInputDeviceClassInfo.clazz = FindClassOrDie(env, "android/view/InputDevice");
@@ -108,9 +107,8 @@
                                                   "String;ZIILandroid/view/KeyCharacterMap;Ljava/"
                                                   "lang/String;Ljava/lang/String;ZZZZZZ)V");
 
-    gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz,
-            "addMotionRange", "(IIFFFFF)V");
-
+    gInputDeviceClassInfo.addMotionRange =
+            GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V");
     return 0;
 }
 
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 403c583..88444cb 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -59,6 +59,7 @@
     jfieldID orientation;
     jfieldID relativeX;
     jfieldID relativeY;
+    jfieldID isResampled;
 } gPointerCoordsClassInfo;
 
 static struct {
@@ -102,6 +103,20 @@
     return eventObj;
 }
 
+jobject android_view_MotionEvent_obtainFromNative(JNIEnv* env, std::unique_ptr<MotionEvent> event) {
+    if (event == nullptr) {
+        return nullptr;
+    }
+    jobject eventObj =
+            env->CallStaticObjectMethod(gMotionEventClassInfo.clazz, gMotionEventClassInfo.obtain);
+    if (env->ExceptionCheck() || !eventObj) {
+        LOGE_EX(env);
+        LOG_ALWAYS_FATAL("An exception occurred while obtaining a Java motion event.");
+    }
+    android_view_MotionEvent_setNativePtr(env, eventObj, event.release());
+    return eventObj;
+}
+
 status_t android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) {
     env->CallVoidMethod(eventObj, gMotionEventClassInfo.recycle);
     if (env->ExceptionCheck()) {
@@ -223,6 +238,8 @@
     outRawPointerCoords->setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y,
                                       env->GetFloatField(pointerCoordsObj,
                                                          gPointerCoordsClassInfo.relativeY));
+    outRawPointerCoords->isResampled =
+            env->GetBooleanField(pointerCoordsObj, gPointerCoordsClassInfo.isResampled);
 
     BitSet64 bits =
             BitSet64(env->GetLongField(pointerCoordsObj, gPointerCoordsClassInfo.mPackedAxisBits));
@@ -440,6 +457,11 @@
         bits.clearBit(axis);
     }
     pointerCoordsFromNative(env, rawPointerCoords, bits, outPointerCoordsObj);
+
+    const bool isResampled = historyPos == HISTORY_CURRENT
+            ? event->isResampled(pointerIndex, event->getHistorySize())
+            : event->isResampled(pointerIndex, historyPos);
+    env->SetBooleanField(outPointerCoordsObj, gPointerCoordsClassInfo.isResampled, isResampled);
 }
 
 static void android_view_MotionEvent_nativeGetPointerProperties(JNIEnv* env, jclass clazz,
@@ -881,6 +903,7 @@
     gPointerCoordsClassInfo.orientation = GetFieldIDOrDie(env, clazz, "orientation", "F");
     gPointerCoordsClassInfo.relativeX = GetFieldIDOrDie(env, clazz, "relativeX", "F");
     gPointerCoordsClassInfo.relativeY = GetFieldIDOrDie(env, clazz, "relativeY", "F");
+    gPointerCoordsClassInfo.isResampled = GetFieldIDOrDie(env, clazz, "isResampled", "Z");
 
     clazz = FindClassOrDie(env, "android/view/MotionEvent$PointerProperties");
 
diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h
index 32a280e..e812136 100644
--- a/core/jni/android_view_MotionEvent.h
+++ b/core/jni/android_view_MotionEvent.h
@@ -28,6 +28,11 @@
  * Returns NULL on error. */
 extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent& event);
 
+/* Obtains an instance of a Java MotionEvent object, taking over the ownership of the provided
+ * native MotionEvent instance. Crashes on error. */
+extern jobject android_view_MotionEvent_obtainFromNative(JNIEnv* env,
+                                                         std::unique_ptr<MotionEvent> event);
+
 /* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
  * Returns NULL if the event is NULL or if it is uninitialized. */
 extern MotionEvent* android_view_MotionEvent_getNativePtr(JNIEnv* env, jobject eventObj);
diff --git a/core/jni/android_view_MotionPredictor.cpp b/core/jni/android_view_MotionPredictor.cpp
new file mode 100644
index 0000000..2c232fa
--- /dev/null
+++ b/core/jni/android_view_MotionPredictor.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define LOG_TAG "MotionPredictor-JNI"
+
+#include <input/Input.h>
+#include <input/MotionPredictor.h>
+
+#include "android_view_MotionEvent.h"
+#include "core_jni_converters.h"
+#include "core_jni_helpers.h"
+
+/**
+ * This file is a bridge from Java to native for MotionPredictor class.
+ * It should be pass-through only. Do not store any state or put any business logic into this file.
+ */
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static struct {
+    jclass clazz;
+} gMotionEventClassInfo;
+
+// ----------------------------------------------------------------------------
+
+static void release(void* ptr) {
+    delete reinterpret_cast<MotionPredictor*>(ptr);
+}
+
+static jlong android_view_MotionPredictor_nativeGetNativeMotionPredictorFinalizer(JNIEnv* env,
+                                                                                  jclass clazz) {
+    return reinterpret_cast<jlong>(release);
+}
+
+static jlong android_view_MotionPredictor_nativeInitialize(JNIEnv* env, jclass clazz,
+                                                           jint offsetNanos) {
+    const nsecs_t offset = static_cast<nsecs_t>(offsetNanos);
+    return reinterpret_cast<jlong>(new MotionPredictor(offset));
+}
+
+static void android_view_MotionPredictor_nativeRecord(JNIEnv* env, jclass clazz, jlong ptr,
+                                                      jobject event) {
+    MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
+    MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, event);
+    predictor->record(*motionEvent);
+}
+
+static jobject android_view_MotionPredictor_nativePredict(JNIEnv* env, jclass clazz, jlong ptr,
+                                                          jlong predictionTimeNanos) {
+    MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
+    return toJavaArray(env, predictor->predict(static_cast<nsecs_t>(predictionTimeNanos)),
+                       gMotionEventClassInfo.clazz, &android_view_MotionEvent_obtainFromNative);
+}
+
+static jboolean android_view_MotionPredictor_nativeIsPredictionAvailable(JNIEnv* env, jclass clazz,
+                                                                         jlong ptr, jint deviceId,
+                                                                         jint source) {
+    MotionPredictor* predictor = reinterpret_cast<MotionPredictor*>(ptr);
+    return predictor->isPredictionAvailable(static_cast<int32_t>(deviceId),
+                                            static_cast<int32_t>(source));
+}
+
+// ----------------------------------------------------------------------------
+
+static const std::array<JNINativeMethod, 5> gMotionPredictorMethods{{
+        /* name, signature, funcPtr */
+        {"nativeInitialize", "(I)J", (void*)android_view_MotionPredictor_nativeInitialize},
+        {"nativeGetNativeMotionPredictorFinalizer", "()J",
+         (void*)android_view_MotionPredictor_nativeGetNativeMotionPredictorFinalizer},
+        {"nativeRecord", "(JLandroid/view/MotionEvent;)V",
+         (void*)android_view_MotionPredictor_nativeRecord},
+        {"nativePredict", "(JJ)[Landroid/view/MotionEvent;",
+         (void*)android_view_MotionPredictor_nativePredict},
+        {"nativeIsPredictionAvailable", "(JII)Z",
+         (void*)android_view_MotionPredictor_nativeIsPredictionAvailable},
+}};
+
+int register_android_view_MotionPredictor(JNIEnv* env) {
+    jclass motionEventClazz = FindClassOrDie(env, "android/view/MotionEvent");
+    gMotionEventClassInfo.clazz = MakeGlobalRefOrDie(env, motionEventClazz);
+    return RegisterMethodsOrDie(env, "android/view/MotionPredictor", gMotionPredictorMethods.data(),
+                                gMotionPredictorMethods.size());
+}
+
+} // namespace android
diff --git a/core/jni/core_jni_converters.h b/core/jni/core_jni_converters.h
new file mode 100644
index 0000000..cb9bdf7
--- /dev/null
+++ b/core/jni/core_jni_converters.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#pragma once
+
+#include <nativehelper/scoped_local_ref.h>
+
+template <class T>
+static jobject toJavaArray(JNIEnv* env, std::vector<T>&& list, jclass clazz,
+                           jobject (*convert)(JNIEnv* env, T)) {
+    jobjectArray arr = env->NewObjectArray(list.size(), clazz, nullptr);
+    LOG_ALWAYS_FATAL_IF(arr == nullptr);
+    for (size_t i = 0; i < list.size(); i++) {
+        T& t = list[i];
+        ScopedLocalRef<jobject> javaObj(env, convert(env, std::move(t)));
+        env->SetObjectArrayElement(arr, i, javaObj.get());
+    }
+    return arr;
+}
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a4d6fdd..72657a0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2344,6 +2344,24 @@
          display, this value should be true. -->
     <bool name="config_perDisplayFocusEnabled">false</bool>
 
+    <!-- Whether the system enables motion prediction. Only enable this after confirming that the
+         model works well on your device. To enable system-based prediction, set this value to true.
+          -->
+    <bool name="config_enableMotionPrediction">true</bool>
+
+    <!-- Additional offset to use for motion prediction, in nanoseconds. A positive number indicates
+         that the prediction will take place further in the future. For example, suppose a
+         MotionEvent arrives with timestamp t=1, and the current expected presentation time is t=2.
+         Typically, the prediction will target the presentation time, t=2. If you'd like to make
+         prediction more aggressive, you could set the offset to a positive number.
+         Setting the offset to 1 here would mean that the prediction will be done for time t=3.
+         A negative number may also be provided, to make the prediction less aggressive. In general,
+         the offset here should represent some built-in hardware delays that may not be accounted
+         for by the "expected present time". See also:
+         https://developer.android.com/reference/android/view/
+                  Choreographer.FrameTimeline#getExpectedPresentationTimeNanos() -->
+    <integer name="config_motionPredictionOffsetNanos">0</integer>
+
     <!-- Whether a software navigation bar should be shown. NOTE: in the future this may be
          autodetected from the Configuration. -->
     <bool name="config_showNavigationBar">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 23f45ea..0e314a7a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1684,6 +1684,8 @@
   <java-symbol type="bool" name="config_lockUiMode" />
   <java-symbol type="bool" name="config_reverseDefaultRotation" />
   <java-symbol type="bool" name="config_perDisplayFocusEnabled" />
+  <java-symbol type="bool" name="config_enableMotionPrediction" />
+  <java-symbol type="integer" name="config_motionPredictionOffsetNanos" />
   <java-symbol type="bool" name="config_showNavigationBar" />
   <java-symbol type="bool" name="config_supportAutoRotation" />
   <java-symbol type="bool" name="config_dockedStackDividerFreeSnapMode" />
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
index 7287579..973b904 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutChooserActivityTest.java
@@ -20,16 +20,19 @@
 import static androidx.test.espresso.action.ViewActions.click;
 import static androidx.test.espresso.action.ViewActions.doubleClick;
 import static androidx.test.espresso.action.ViewActions.scrollTo;
+import static androidx.test.espresso.action.ViewActions.swipeUp;
 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
 import static androidx.test.espresso.assertion.ViewAssertions.matches;
 import static androidx.test.espresso.matcher.RootMatchers.isDialog;
 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
 import static androidx.test.espresso.matcher.ViewMatchers.withId;
 import static androidx.test.espresso.matcher.ViewMatchers.withText;
 
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.endsWith;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -39,7 +42,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.os.Bundle;
 import android.os.Handler;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IAccessibilityManager;
@@ -52,11 +54,12 @@
 import com.android.internal.R;
 import com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity;
 
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 import java.util.Collections;
 
@@ -65,57 +68,93 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityShortcutChooserActivityTest {
+    private static final String ONE_HANDED_MODE = "One-Handed mode";
     private static final String TEST_LABEL = "TEST_LABEL";
-    private static final Context sContext =
-            InstrumentationRegistry.getInstrumentation().getContext();
-    private ActivityScenario<TestAccessibilityShortcutChooserActivity> mScenario;
-    private static IAccessibilityManager sAccessibilityManagerService;
+    private static final ComponentName TEST_COMPONENT_NAME = new ComponentName("package", "class");
 
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Mock
     private AccessibilityServiceInfo mAccessibilityServiceInfo;
-
     @Mock
     private ResolveInfo mResolveInfo;
-
     @Mock
     private ServiceInfo mServiceInfo;
-
     @Mock
     private ApplicationInfo mApplicationInfo;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        sAccessibilityManagerService = mock(IAccessibilityManager.class);
-
-        when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
-        mResolveInfo.serviceInfo = mServiceInfo;
-        mServiceInfo.applicationInfo = mApplicationInfo;
-        when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
-        when(mAccessibilityServiceInfo.getComponentName()).thenReturn(
-                new ComponentName("package", "class"));
-        when(sAccessibilityManagerService.getInstalledAccessibilityServiceList(
-                anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
-
-        mScenario = ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
-    }
+    @Mock
+    private IAccessibilityManager mAccessibilityManagerService;
 
     @Test
-    public void doubleClickServiceTargetAndClickDenyButton_permissionDialogDoesNotExist() {
-        mScenario.moveToState(Lifecycle.State.CREATED);
-        mScenario.moveToState(Lifecycle.State.STARTED);
-        mScenario.moveToState(Lifecycle.State.RESUMED);
+    public void doubleClickTestServiceAndClickDenyButton_permissionDialogDoesNotExist()
+            throws Exception {
+        configureTestService();
+        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
+                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+        scenario.moveToState(Lifecycle.State.CREATED);
+        scenario.moveToState(Lifecycle.State.STARTED);
+        scenario.moveToState(Lifecycle.State.RESUMED);
+
         onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
                 isDialog()).check(matches(isDisplayed()));
         onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
-
         onView(withText(TEST_LABEL)).perform(scrollTo(), doubleClick());
         onView(withId(R.id.accessibility_permission_enable_deny_button)).perform(scrollTo(),
                 click());
 
         onView(withId(R.id.accessibility_permissionDialog_title)).inRoot(isDialog()).check(
                 doesNotExist());
-        mScenario.moveToState(Lifecycle.State.DESTROYED);
+        scenario.moveToState(Lifecycle.State.DESTROYED);
+    }
+
+    @Test
+    public void popEditShortcutMenuList_oneHandedModeEnabled_shouldBeInListView() {
+        TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
+                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+        scenario.moveToState(Lifecycle.State.CREATED);
+        scenario.moveToState(Lifecycle.State.STARTED);
+        scenario.moveToState(Lifecycle.State.RESUMED);
+
+        onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+                isDialog()).check(matches(isDisplayed()));
+        onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+        onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(matches(isDisplayed()));
+        scenario.moveToState(Lifecycle.State.DESTROYED);
+    }
+
+    @Test
+    public void popEditShortcutMenuList_oneHandedModeDisabled_shouldNotBeInListView() {
+        TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+        final ActivityScenario<TestAccessibilityShortcutChooserActivity> scenario =
+                ActivityScenario.launch(TestAccessibilityShortcutChooserActivity.class);
+        scenario.moveToState(Lifecycle.State.CREATED);
+        scenario.moveToState(Lifecycle.State.STARTED);
+        scenario.moveToState(Lifecycle.State.RESUMED);
+
+        onView(withText(R.string.accessibility_select_shortcut_menu_title)).inRoot(
+                isDialog()).check(matches(isDisplayed()));
+        onView(withText(R.string.edit_accessibility_shortcut_menu_button)).perform(click());
+        onView(allOf(withClassName(endsWith("ListView")), isDisplayed())).perform(swipeUp());
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        onView(withText(ONE_HANDED_MODE)).inRoot(isDialog()).check(doesNotExist());
+        scenario.moveToState(Lifecycle.State.DESTROYED);
+    }
+
+    private void configureTestService() throws Exception {
+        when(mAccessibilityServiceInfo.getResolveInfo()).thenReturn(mResolveInfo);
+        mResolveInfo.serviceInfo = mServiceInfo;
+        mServiceInfo.applicationInfo = mApplicationInfo;
+        when(mResolveInfo.loadLabel(any(PackageManager.class))).thenReturn(TEST_LABEL);
+        when(mAccessibilityServiceInfo.getComponentName()).thenReturn(TEST_COMPONENT_NAME);
+        when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(
+                anyInt())).thenReturn(Collections.singletonList(mAccessibilityServiceInfo));
+
+        TestAccessibilityShortcutChooserActivity.setupForTesting(mAccessibilityManagerService);
     }
 
     /**
@@ -123,19 +162,18 @@
      */
     public static class TestAccessibilityShortcutChooserActivity extends
             AccessibilityShortcutChooserActivity {
-        private AccessibilityManager mAccessibilityManager;
+        private static IAccessibilityManager sAccessibilityManagerService;
 
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            mAccessibilityManager = new AccessibilityManager(sContext, new Handler(getMainLooper()),
-                    sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
-            super.onCreate(savedInstanceState);
+        public static void setupForTesting(IAccessibilityManager accessibilityManagerService) {
+            sAccessibilityManagerService = accessibilityManagerService;
         }
 
         @Override
         public Object getSystemService(String name) {
-            if (Context.ACCESSIBILITY_SERVICE.equals(name)) {
-                return mAccessibilityManager;
+            if (Context.ACCESSIBILITY_SERVICE.equals(name)
+                    && sAccessibilityManagerService != null) {
+                return new AccessibilityManager(this, new Handler(getMainLooper()),
+                        sAccessibilityManagerService, /* userId= */ 0, /* serviceConnect= */ true);
             }
 
             return super.getSystemService(name);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index a68c392..9763679 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -208,6 +208,17 @@
         when(mAlertDialog.getWindow()).thenReturn(window);
 
         when(mTextToSpeech.getVoice()).thenReturn(mVoice);
+
+        // Clears the sFrameworkShortcutFeaturesMap field which was not properly initialized
+        // during testing.
+        try {
+            Field field = AccessibilityShortcutController.class.getDeclaredField(
+                    "sFrameworkShortcutFeaturesMap");
+            field.setAccessible(true);
+            field.set(window, null);
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to set sFrameworkShortcutFeaturesMap", e);
+        }
     }
 
     @AfterClass
@@ -433,11 +444,10 @@
     }
 
     @Test
-    public void getFrameworkFeatureMap_shouldBeNonNullAndUnmodifiable() {
-        Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
+    public void getFrameworkFeatureMap_shouldBeUnmodifiable() {
+        final Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
                 frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
-        assertTrue("Framework features not supported", frameworkFeatureMap.size() > 0);
 
         try {
             frameworkFeatureMap.clear();
@@ -448,19 +458,40 @@
     }
 
     @Test
-    public void getFrameworkFeatureMap_containsExpectedKey() {
-        Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
+    public void getFrameworkFeatureMap_containsExpectedDefaultKeys() {
+        final Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
                 frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
 
         assertTrue(frameworkFeatureMap.containsKey(COLOR_INVERSION_COMPONENT_NAME));
         assertTrue(frameworkFeatureMap.containsKey(DALTONIZER_COMPONENT_NAME));
-        assertTrue(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
         assertTrue(frameworkFeatureMap.containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME));
         assertTrue(frameworkFeatureMap.containsKey(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME));
     }
 
     @Test
+    public void getFrameworkFeatureMap_oneHandedModeEnabled_containsExpectedKey() {
+        TestUtils.setOneHandedModeEnabled(this, /* enabled= */ true);
+
+        final Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
+                frameworkFeatureMap =
+                AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+        assertTrue(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+    }
+
+    @Test
+    public void getFrameworkFeatureMap_oneHandedModeDisabled_containsExpectedKey() {
+        TestUtils.setOneHandedModeEnabled(this, /* enabled= */ false);
+
+        final Map<ComponentName, AccessibilityShortcutController.FrameworkFeatureInfo>
+                frameworkFeatureMap =
+                AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
+
+        assertFalse(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+    }
+
+    @Test
     public void testOnAccessibilityShortcut_forServiceWithNoSummary_doesNotCrash()
             throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java
new file mode 100644
index 0000000..ff014ad
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/accessibility/TestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.accessibility;
+
+import com.android.internal.os.RoSystemProperties;
+
+import java.lang.reflect.Field;
+
+/**
+ * Test utility methods.
+ */
+public class TestUtils {
+
+    /**
+     * Sets the {@code enabled} of the given OneHandedMode flags to simulate device behavior.
+     */
+    public static void setOneHandedModeEnabled(Object obj, boolean enabled) {
+        try {
+            final Field field = RoSystemProperties.class.getDeclaredField(
+                    "SUPPORT_ONE_HANDED_MODE");
+            field.setAccessible(true);
+            field.setBoolean(obj, enabled);
+        } catch (ReflectiveOperationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 0f8616d..247d484 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -249,6 +249,7 @@
 key 228   KEYBOARD_BACKLIGHT_TOGGLE
 key 229   KEYBOARD_BACKLIGHT_DOWN
 key 230   KEYBOARD_BACKLIGHT_UP
+key 248   MUTE
 
 key 256   BUTTON_1
 key 257   BUTTON_2
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 87fa63d..00e13c9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -191,10 +191,25 @@
      */
     void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+                null /* pairedActivityToken */);
+    }
+
+    /**
+     * @param ownerToken The token of the activity that creates this task fragment. It does not
+     *                   have to be a child of this task fragment, but must belong to the same task.
+     * @param pairedActivityToken The token of the activity that will be reparented to this task
+     *                            fragment. When it is not {@code null}, the task fragment will be
+     *                            positioned right above it.
+     */
+    void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+            @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode,
+            @Nullable IBinder pairedActivityToken) {
         final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
                 getOrganizerToken(), fragmentToken, ownerToken)
                 .setInitialBounds(bounds)
                 .setWindowingMode(windowingMode)
+                .setPairedActivityToken(pairedActivityToken)
                 .build();
         createTaskFragment(wct, fragmentOptions);
     }
@@ -216,8 +231,10 @@
     private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
             @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
             @WindowingMode int windowingMode, @NonNull Activity activity) {
-        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
-        wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
+        final IBinder reparentActivityToken = activity.getActivityToken();
+        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode,
+                reparentActivityToken);
+        wct.reparentActivityToTaskFragment(fragmentToken, reparentActivityToken);
     }
 
     void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 8c4de70..868ced0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1507,7 +1507,7 @@
      * Returns the active split that has the provided containers as primary and secondary or as
      * secondary and primary, if available.
      */
-    @VisibleForTesting
+    @GuardedBy("mLock")
     @Nullable
     SplitContainer getActiveSplitForContainers(
             @NonNull TaskFragmentContainer firstContainer,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 6f2fe80..d9abe8e0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -268,10 +268,11 @@
             container = mController.newContainer(activity, taskId);
             final int windowingMode = mController.getTaskContainer(taskId)
                     .getWindowingModeForSplitTaskFragment(bounds);
-            createTaskFragment(wct, container.getTaskFragmentToken(), activity.getActivityToken(),
-                    bounds, windowingMode);
+            final IBinder reparentActivityToken = activity.getActivityToken();
+            createTaskFragment(wct, container.getTaskFragmentToken(), reparentActivityToken,
+                    bounds, windowingMode, reparentActivityToken);
             wct.reparentActivityToTaskFragment(container.getTaskFragmentToken(),
-                    activity.getActivityToken());
+                    reparentActivityToken);
         } else {
             resizeTaskFragmentIfRegistered(wct, container, bounds);
             final int windowingMode = mController.getTaskContainer(taskId)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 076856c..17814c6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -141,12 +141,26 @@
         mToken = new Binder("TaskFragmentContainer");
         mTaskContainer = taskContainer;
         if (pairedPrimaryContainer != null) {
+            // The TaskFragment will be positioned right above the paired container.
             if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
                 throw new IllegalArgumentException(
                         "pairedPrimaryContainer must be in the same Task");
             }
             final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
             taskContainer.mContainers.add(primaryIndex + 1, this);
+        } else if (pendingAppearedActivity != null) {
+            // The TaskFragment will be positioned right above the pending appeared Activity. If any
+            // existing TaskFragment is empty with pending Intent, it is likely that the Activity of
+            // the pending Intent hasn't been created yet, so the new Activity should be below the
+            // empty TaskFragment.
+            int i = taskContainer.mContainers.size() - 1;
+            for (; i >= 0; i--) {
+                final TaskFragmentContainer container = taskContainer.mContainers.get(i);
+                if (!container.isEmpty() || container.getPendingAppearedIntent() == null) {
+                    break;
+                }
+            }
+            taskContainer.mContainers.add(i + 1, this);
         } else {
             taskContainer.mContainers.add(this);
         }
@@ -500,6 +514,8 @@
         }
 
         if (!shouldFinishDependent) {
+            // Always finish the placeholder when the primary is finished.
+            finishPlaceholderIfAny(wct, presenter);
             return;
         }
 
@@ -526,6 +542,28 @@
         mActivitiesToFinishOnExit.clear();
     }
 
+    @GuardedBy("mController.mLock")
+    private void finishPlaceholderIfAny(@NonNull WindowContainerTransaction wct,
+            @NonNull SplitPresenter presenter) {
+        final List<TaskFragmentContainer> containersToRemove = new ArrayList<>();
+        for (TaskFragmentContainer container : mContainersToFinishOnExit) {
+            if (container.mIsFinished) {
+                continue;
+            }
+            final SplitContainer splitContainer = mController.getActiveSplitForContainers(
+                    this, container);
+            if (splitContainer != null && splitContainer.isPlaceholderContainer()
+                    && splitContainer.getSecondaryContainer() == container) {
+                // Remove the placeholder secondary TaskFragment.
+                containersToRemove.add(container);
+            }
+        }
+        mContainersToFinishOnExit.removeAll(containersToRemove);
+        for (TaskFragmentContainer container : containersToRemove) {
+            container.finish(false /* shouldFinishDependent */, presenter, wct, mController);
+        }
+    }
+
     boolean isFinished() {
         return mIsFinished;
     }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 7d9d8b0..78b85e6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -154,17 +154,52 @@
                 null /* pendingAppearedIntent */, taskContainer, mController,
                 null /* pairedPrimaryContainer */);
         doReturn(container1).when(mController).getContainerWithActivity(mActivity);
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
 
         // The activity is requested to be reparented, so don't finish it.
-        container0.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
+        container0.finish(true /* shouldFinishDependent */, mPresenter, mTransaction, mController);
 
         verify(mTransaction, never()).finishActivity(any());
-        verify(mPresenter).deleteTaskFragment(wct, container0.getTaskFragmentToken());
+        verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
         verify(mController).removeContainer(container0);
     }
 
     @Test
+    public void testFinish_alwaysFinishPlaceholder() {
+        // Register container1 as a placeholder
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
+                null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryContainer */);
+        final TaskFragmentInfo info0 = createMockTaskFragmentInfo(container0, mActivity);
+        container0.setInfo(mTransaction, info0);
+        final Activity placeholderActivity = createMockActivity();
+        final TaskFragmentContainer container1 = new TaskFragmentContainer(placeholderActivity,
+                null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryContainer */);
+        final TaskFragmentInfo info1 = createMockTaskFragmentInfo(container1, placeholderActivity);
+        container1.setInfo(mTransaction, info1);
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
+        final SplitPlaceholderRule rule = new SplitPlaceholderRule.Builder(new Intent(),
+                mActivity::equals, (java.util.function.Predicate) i -> false,
+                (java.util.function.Predicate) w -> true)
+                .setDefaultSplitAttributes(splitAttributes)
+                .build();
+        mController.registerSplit(mTransaction, container0, mActivity, container1, rule,
+                splitAttributes);
+
+        // The placeholder TaskFragment should be finished even if the primary is finished with
+        // shouldFinishDependent = false.
+        container0.finish(false /* shouldFinishDependent */, mPresenter, mTransaction, mController);
+
+        assertTrue(container0.isFinished());
+        assertTrue(container1.isFinished());
+        verify(mPresenter).deleteTaskFragment(mTransaction, container0.getTaskFragmentToken());
+        verify(mPresenter).deleteTaskFragment(mTransaction, container1.getTaskFragmentToken());
+        verify(mController).removeContainer(container0);
+        verify(mController).removeContainer(container1);
+    }
+
+    @Test
     public void testSetInfo() {
         final TaskContainer taskContainer = createTestTaskContainer();
         // Pending activity should be cleared when it has appeared on server side.
@@ -493,8 +528,6 @@
         final TaskFragmentContainer tf1 = new TaskFragmentContainer(
                 null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
                 null /* pairedPrimaryTaskFragment */);
-        taskContainer.mContainers.add(tf0);
-        taskContainer.mContainers.add(tf1);
 
         // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
         // right above tf0.
@@ -506,6 +539,26 @@
     }
 
     @Test
+    public void testNewContainerWithPairedPendingAppearedActivity() {
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final TaskFragmentContainer tf0 = new TaskFragmentContainer(
+                createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryTaskFragment */);
+        final TaskFragmentContainer tf1 = new TaskFragmentContainer(
+                null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+                null /* pairedPrimaryTaskFragment */);
+
+        // When tf2 is created with pendingAppearedActivity, tf2 should be inserted below any
+        // TaskFragment without any Activity.
+        final TaskFragmentContainer tf2 = new TaskFragmentContainer(
+                createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryTaskFragment */);
+        assertEquals(0, taskContainer.indexOf(tf0));
+        assertEquals(1, taskContainer.indexOf(tf2));
+        assertEquals(2, taskContainer.indexOf(tf1));
+    }
+
+    @Test
     public void testIsVisible() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index 5e898e8..4f3facb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -21,12 +21,7 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -64,17 +59,12 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                removeAllTasksButHome()
-                device.wakeUpAndGoToHomeScreen()
                 pipApp.launchViaIntent(wmHelper)
                 pipApp.enableAutoEnterForPipActivity()
             }
             teardown {
                 // close gracefully so that onActivityUnpinned() can be called before force exit
                 pipApp.closePipWindow(wmHelper)
-
-                setRotation(PlatformConsts.Rotation.ROTATION_0)
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
             transitions { tapl.goHome() }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index 79feeaa..bc9fc73 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -20,11 +20,7 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
-import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -61,15 +57,10 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                device.wakeUpAndGoToHomeScreen()
-                device.wakeUpAndGoToHomeScreen()
                 pipApp.launchViaIntent(wmHelper)
                 pipApp.enableEnterPipOnUserLeaveHint()
             }
             teardown {
-                setRotation(PlatformConsts.Rotation.ROTATION_0)
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
             transitions { tapl.goHome() }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 1a76142..1524b16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -21,10 +21,7 @@
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.FlickerTestFactory
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.service.PlatformConsts
 import org.junit.FixMethodOrder
@@ -63,13 +60,9 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                device.wakeUpAndGoToHomeScreen()
                 pipApp.launchViaIntent(wmHelper)
             }
             teardown {
-                setRotation(PlatformConsts.Rotation.ROTATION_0)
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
             transitions { pipApp.clickEnterPipButton(wmHelper) }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index a4c8d6f..da16240 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -26,11 +26,8 @@
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.navBarLayerPositionAtStartAndEnd
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
 import com.android.server.wm.traces.common.ComponentNameMatcher
@@ -79,9 +76,6 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                device.wakeUpAndGoToHomeScreen()
-
                 // Launch a portrait only app on the fullscreen stack
                 testApp.launchViaIntent(
                     wmHelper,
@@ -95,8 +89,6 @@
                 )
             }
             teardown {
-                setRotation(PlatformConsts.Rotation.ROTATION_0)
-                RemoveAllTasksButHomeRule.removeAllTasksButHome()
                 pipApp.exit(wmHelper)
                 testApp.exit(wmHelper)
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 12d6362..737e65c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -58,7 +58,6 @@
             }
             teardown {
                 imeApp.exit(wmHelper)
-                setRotation(PlatformConsts.Rotation.ROTATION_0)
             }
             transitions {
                 // open the soft keyboard
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index 0e0be79..a9fe93d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -72,8 +72,6 @@
                 pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
             }
             teardown {
-                setRotation(PlatformConsts.Rotation.ROTATION_0)
-                removeAllTasksButHome()
                 pipApp.exit(wmHelper)
             }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 7d5dd89..d7107db 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -26,10 +26,7 @@
 import com.android.server.wm.flicker.FlickerTestFactory
 import com.android.server.wm.flicker.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
 import com.android.server.wm.traces.common.service.PlatformConsts
@@ -58,9 +55,6 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             setup {
-                removeAllTasksButHome()
-                device.wakeUpAndGoToHomeScreen()
-
                 // Launch the PiP activity fixed as landscape.
                 pipApp.launchViaIntent(
                     wmHelper,
@@ -80,8 +74,6 @@
             }
             teardown {
                 pipApp.exit(wmHelper)
-                setRotation(PlatformConsts.Rotation.ROTATION_0)
-                removeAllTasksButHome()
             }
             transitions {
                 // Launch the activity back into fullscreen and ensure that it is now in landscape
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 761edf6..4475aed 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3500,6 +3500,19 @@
     }
 
     /**
+     * Suspends the use of LE Audio.
+     *
+     * @param enable {@code true} to suspend le audio, {@code false} to unsuspend
+     *
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK)
+    public void setLeAudioSuspended(boolean enable) {
+        AudioSystem.setParameters("LeAudioSuspended=" + enable);
+    }
+
+    /**
      * Gets a variable number of parameter values from audio hardware.
      *
      * @param keys list of parameters
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index ed2fd20..d55d287 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -49,6 +49,7 @@
     void onTimeShiftCurrentPositionChanged(long timeMs, int seq);
     void onAitInfoUpdated(in AitInfo aitInfo, int seq);
     void onSignalStrength(int stength, int seq);
+    void onTvMessage(in String type, in Bundle data, int seq);
 
     void onTuned(in Uri channelUri, int seq);
     // For the recording session
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index b2a8d1c..8216622 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -58,4 +58,7 @@
     // For ad response
     void onAdResponse(in AdResponse response);
     void onAdBufferConsumed(in AdBuffer buffer);
+
+    // For messages sent from the TV input
+    void onTvMessage(in String type, in Bundle data);
 }
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 690fcb1..e6da1a3 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
+import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -130,6 +131,20 @@
         VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING, VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN})
     public @interface VideoUnavailableReason {}
 
+    /**
+     * @hide
+     */
+    public static final String TV_MESSAGE_TYPE_WATERMARK = "Watermark";
+    /**
+     * @hide
+     */
+    public static final String TV_MESSAGE_TYPE_ATSC_CC = "ATSC_CC";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_ATSC_CC})
+    public @interface TvMessageType {}
+
     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
     static final int VIDEO_UNAVAILABLE_REASON_END = 18;
 
@@ -690,6 +705,17 @@
         public void onTuned(Session session, Uri channelUri) {
         }
 
+        /**
+         * This is called when the session receives a new Tv Message
+         *
+         * @param type the type of {@link TvMessageType}
+         * @param data the raw data of the message
+         * @hide
+         */
+        public void onTvMessage(Session session, @TvInputManager.TvMessageType String type,
+                Bundle data) {
+        }
+
         // For the recording session only
         /**
          * This is called when the current recording session has stopped recording and created a
@@ -919,6 +945,19 @@
             });
         }
 
+        void postTvMessage(String type, Bundle data) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mSessionCallback.onTvMessage(mSession, type, data);
+                    if (mSession.mIAppNotificationEnabled
+                            && mSession.getInteractiveAppSession() != null) {
+                        mSession.getInteractiveAppSession().notifyTvMessage(type, data);
+                    }
+                }
+            });
+        }
+
         // For the recording session only
         void postRecordingStopped(final Uri recordedProgramUri) {
             mHandler.post(new Runnable() {
@@ -1379,6 +1418,18 @@
             }
 
             @Override
+            public void onTvMessage(String type, Bundle data, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postTvMessage(type, data);
+                }
+            }
+
+            @Override
             public void onRecordingStopped(Uri recordedProgramUri, int seq) {
                 synchronized (mSessionCallbackRecordMap) {
                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index c46cdbc..15f511b 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -2063,6 +2063,27 @@
         }
 
         /**
+         * Informs the application of the raw data from the TV message.
+         * @param type The {@link TvInputManager.TvMessageType} of message that was sent.
+         * @param data The data sent with the message.
+         * @hide
+         */
+        public void notifyTvMessage(@TvInputManager.TvMessageType String type, Bundle data) {
+        }
+
+        /**
+         * Called when the application enables or disables the detection of the specified message
+         * type.
+         * @param type The {@link TvInputManager.TvMessageType} of message that was sent.
+         * @param enabled {@code true} if you want to enable TV message detecting
+         *                {@code false} otherwise.
+         * @hide
+         */
+        public void onSetTvMessageEnabled(@TvInputManager.TvMessageType String type,
+                boolean enabled) {
+        }
+
+        /**
          * Called when the application requests to tune to a given channel for TV program recording.
          *
          * <p>The application may call this method before starting or after stopping recording, but
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index ff3d06c..2fdbc3b 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -653,6 +653,17 @@
         mOnUnhandledInputEventListener = listener;
     }
 
+    /**
+     * Enables or disables TV message detecting in the streams of bound TV input.
+     *
+     * @param type The type of {@link android.media.tv.TvInputManager.TvMessageType}
+     * @param enabled {@code true} if you want to enable TV message detecting
+     *                {@code false} otherwise.
+     * @hide
+     */
+    public void setTvMessageEnabled(@TvInputManager.TvMessageType String type, boolean enabled) {
+    }
+
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (super.dispatchKeyEvent(event)) {
@@ -1094,6 +1105,17 @@
          */
         public void onTuned(@NonNull String inputId, @NonNull Uri channelUri) {
         }
+
+        /**
+         * This is called when the session has been tuned to the given channel.
+         *
+         * @param type The type of {@link android.media.tv.TvInputManager.TvMessageType}
+         * @param data The raw data of the message
+         * @hide
+         */
+        public void onTvMessage(@NonNull String inputId, @TvInputManager.TvMessageType String type,
+                Bundle data) {
+        }
     }
 
     /**
@@ -1432,5 +1454,19 @@
                 mCallback.onTuned(mInputId, channelUri);
             }
         }
+
+        @Override
+        public void onTvMessage(Session session, String type, Bundle data) {
+            if (DEBUG) {
+                Log.d(TAG, "onTvMessage(type=" + type + ", data=" + data + ")");
+            }
+            if (this != mSessionCallback) {
+                Log.w(TAG, "onTvMessage - session not created");
+                return;
+            }
+            if (mCallback != null) {
+                mCallback.onTvMessage(mInputId, type, data);
+            }
+        }
     }
 }
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 3b272daa..5a0ac84 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -70,6 +70,7 @@
     void notifySignalStrength(in IBinder sessionToken, int stength, int userId);
     void notifyRecordingStarted(in IBinder sessionToken, in String recordingId, int userId);
     void notifyRecordingStopped(in IBinder sessionToken, in String recordingId, int userId);
+    void notifyTvMessage(in IBinder sessionToken, in String type, in Bundle data, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
     void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index bc09cea..20ba57b 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -59,6 +59,7 @@
     void notifySignalStrength(int strength);
     void notifyRecordingStarted(in String recordingId);
     void notifyRecordingStopped(in String recordingId);
+    void notifyTvMessage(in String type, in Bundle data);
     void setSurface(in Surface surface);
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index af031dc..a55e1ac 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -86,8 +86,9 @@
     private static final int DO_NOTIFY_RECORDING_STARTED = 30;
     private static final int DO_NOTIFY_RECORDING_STOPPED = 31;
     private static final int DO_NOTIFY_AD_BUFFER_CONSUMED = 32;
-    private static final int DO_SEND_RECORDING_INFO = 33;
-    private static final int DO_SEND_RECORDING_INFO_LIST = 34;
+    private static final int DO_NOTIFY_TV_MESSAGE = 33;
+    private static final int DO_SEND_RECORDING_INFO = 34;
+    private static final int DO_SEND_RECORDING_INFO_LIST = 35;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -213,6 +214,12 @@
                 mSessionImpl.notifyTracksChanged((List<TvTrackInfo>) msg.obj);
                 break;
             }
+            case DO_NOTIFY_TV_MESSAGE: {
+                SomeArgs args = (SomeArgs) msg.obj;
+                mSessionImpl.notifyTvMessage((String) args.arg1, (Bundle) args.arg2);
+                args.recycle();
+                break;
+            }
             case DO_NOTIFY_VIDEO_AVAILABLE: {
                 mSessionImpl.notifyVideoAvailable();
                 break;
@@ -390,6 +397,12 @@
     }
 
     @Override
+    public void notifyTvMessage(String type, Bundle data) {
+        mCaller.executeOrSendMessage(
+                mCaller.obtainMessageOO(DO_NOTIFY_TRACK_SELECTED, type, data));
+    }
+
+    @Override
     public void notifyTracksChanged(List<TvTrackInfo> tracks) {
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_TRACKS_CHANGED, tracks));
     }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index fa60b66..f4847f7 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -1504,6 +1504,21 @@
             }
         }
 
+        /**
+         * Notifies Interactive APP session when a new TV message is received.
+         */
+        public void notifyTvMessage(String type, Bundle data) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyTvMessage(mToken, type, data, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private void flushPendingEventsLocked() {
             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
 
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 1fa0aaa..3ca9f2f 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -641,6 +641,14 @@
          * @hide
          */
         public void onAdBufferConsumed(AdBuffer buffer) {
+
+        }
+
+        /**
+         * Called when a tv message is received
+         * @hide
+         */
+        public void onTvMessage(@NonNull String type, @NonNull Bundle data) {
         }
 
         @Override
@@ -1289,6 +1297,13 @@
             onAdResponse(response);
         }
 
+        void notifyTvMessage(String type, Bundle data) {
+            if (DEBUG) {
+                Log.d(TAG, "notifyTvMessage (type=" + type + ", data= " + data + ")");
+            }
+            onTvMessage(type, data);
+        }
+
         /**
          * Calls {@link #onAdBufferConsumed}.
          */
diff --git a/opengl/java/android/opengl/Matrix.java b/opengl/java/android/opengl/Matrix.java
index ce3f57e..88896c3 100644
--- a/opengl/java/android/opengl/Matrix.java
+++ b/opengl/java/android/opengl/Matrix.java
@@ -16,6 +16,8 @@
 
 package android.opengl;
 
+import androidx.annotation.NonNull;
+
 /**
  * Matrix math utilities. These methods operate on OpenGL ES format
  * matrices and vectors stored in float arrays.
@@ -38,7 +40,11 @@
 public class Matrix {
 
     /** Temporary memory for operations that need temporary matrix data. */
-    private final static float[] sTemp = new float[32];
+    private static final ThreadLocal<float[]> ThreadTmp = new ThreadLocal() {
+        @Override protected float[] initialValue() {
+            return new float[32];
+        }
+    };
 
     /**
      * @deprecated All methods are static, do not instantiate this class.
@@ -46,6 +52,40 @@
     @Deprecated
     public Matrix() {}
 
+    private static boolean overlap(
+            float[] a, int aStart, int aLength, float[] b, int bStart, int bLength) {
+        if (a != b) {
+            return false;
+        }
+
+        if (aStart == bStart) {
+            return true;
+        }
+
+        int aEnd = aStart + aLength;
+        int bEnd = bStart + bLength;
+
+        if (aEnd == bEnd) {
+            return true;
+        }
+
+        if (aStart < bStart && bStart < aEnd) {
+            return true;
+        }
+        if (aStart < bEnd   && bEnd   < aEnd) {
+            return true;
+        }
+
+        if (bStart < aStart && aStart < bEnd) {
+            return true;
+        }
+        if (bStart < aEnd   && aEnd   < bEnd) {
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Multiplies two 4x4 matrices together and stores the result in a third 4x4
      * matrix. In matrix notation: result = lhs x rhs. Due to the way
@@ -53,9 +93,9 @@
      * effect as first multiplying by the rhs matrix, then multiplying by
      * the lhs matrix. This is the opposite of what you might expect.
      * <p>
-     * The same float array may be passed for result, lhs, and/or rhs. However,
-     * the result element values are undefined if the result elements overlap
-     * either the lhs or rhs elements.
+     * The same float array may be passed for result, lhs, and/or rhs. This
+     * operation is expected to do the correct thing if the result elements
+     * overlap with either of the lhs or rhs elements.
      *
      * @param result The float array that holds the result.
      * @param resultOffset The offset into the result array where the result is
@@ -65,20 +105,101 @@
      * @param rhs The float array that holds the right-hand-side matrix.
      * @param rhsOffset The offset into the rhs array where the rhs is stored.
      *
-     * @throws IllegalArgumentException if result, lhs, or rhs are null, or if
-     * resultOffset + 16 > result.length or lhsOffset + 16 > lhs.length or
-     * rhsOffset + 16 > rhs.length.
+     * @throws IllegalArgumentException under any of the following conditions:
+     * result, lhs, or rhs are null;
+     * resultOffset + 16 > result.length
+     * or lhsOffset + 16 > lhs.length
+     * or rhsOffset + 16 > rhs.length;
+     * resultOffset < 0 or lhsOffset < 0 or rhsOffset < 0
      */
-    public static native void multiplyMM(float[] result, int resultOffset,
-            float[] lhs, int lhsOffset, float[] rhs, int rhsOffset);
+    public static void multiplyMM(float[] result, int resultOffset,
+            float[] lhs, int lhsOffset, float[] rhs, int rhsOffset) {
+        // error checking
+        if (result == null) {
+            throw new IllegalArgumentException("result == null");
+        }
+        if (lhs == null) {
+            throw new IllegalArgumentException("lhs == null");
+        }
+        if (rhs == null) {
+            throw new IllegalArgumentException("rhs == null");
+        }
+        if (resultOffset < 0) {
+            throw new IllegalArgumentException("resultOffset < 0");
+        }
+        if (lhsOffset < 0) {
+            throw new IllegalArgumentException("lhsOffset < 0");
+        }
+        if (rhsOffset < 0) {
+            throw new IllegalArgumentException("rhsOffset < 0");
+        }
+        if (result.length < resultOffset + 16) {
+            throw new IllegalArgumentException("result.length < resultOffset + 16");
+        }
+        if (lhs.length < lhsOffset + 16) {
+            throw new IllegalArgumentException("lhs.length < lhsOffset + 16");
+        }
+        if (rhs.length < rhsOffset + 16) {
+            throw new IllegalArgumentException("rhs.length < rhsOffset + 16");
+        }
+
+        // Check for overlap between rhs and result or lhs and result
+        if ( overlap(result, resultOffset, 16, lhs, lhsOffset, 16)
+                || overlap(result, resultOffset, 16, rhs, rhsOffset, 16) ) {
+            float[] tmp = ThreadTmp.get();
+            for (int i=0; i<4; i++) {
+                final float rhs_i0 = rhs[ 4*i + 0 + rhsOffset ];
+                float ri0 = lhs[ 0 + lhsOffset ] * rhs_i0;
+                float ri1 = lhs[ 1 + lhsOffset ] * rhs_i0;
+                float ri2 = lhs[ 2 + lhsOffset ] * rhs_i0;
+                float ri3 = lhs[ 3 + 16 ] * rhs_i0;
+                for (int j=1; j<4; j++) {
+                    final float rhs_ij = rhs[ 4*i + j + rhsOffset];
+                    ri0 += lhs[ 4*j + 0 + lhsOffset ] * rhs_ij;
+                    ri1 += lhs[ 4*j + 1 + lhsOffset ] * rhs_ij;
+                    ri2 += lhs[ 4*j + 2 + lhsOffset ] * rhs_ij;
+                    ri3 += lhs[ 4*j + 3 + lhsOffset ] * rhs_ij;
+                }
+                tmp[ 4*i + 0 ] = ri0;
+                tmp[ 4*i + 1 ] = ri1;
+                tmp[ 4*i + 2 ] = ri2;
+                tmp[ 4*i + 3 ] = ri3;
+            }
+
+            // copy from tmp to result
+            for (int i=0; i < 16; i++) {
+                result[ i + resultOffset ] = tmp[ i ];
+            }
+
+        } else {
+            for (int i=0; i<4; i++) {
+                final float rhs_i0 = rhs[ 4*i + 0 + rhsOffset ];
+                float ri0 = lhs[ 0 + lhsOffset ] * rhs_i0;
+                float ri1 = lhs[ 1 + lhsOffset ] * rhs_i0;
+                float ri2 = lhs[ 2 + lhsOffset ] * rhs_i0;
+                float ri3 = lhs[ 3 + lhsOffset ] * rhs_i0;
+                for (int j=1; j<4; j++) {
+                    final float rhs_ij = rhs[ 4*i + j + rhsOffset];
+                    ri0 += lhs[ 4*j + 0 + lhsOffset ] * rhs_ij;
+                    ri1 += lhs[ 4*j + 1 + lhsOffset ] * rhs_ij;
+                    ri2 += lhs[ 4*j + 2 + lhsOffset ] * rhs_ij;
+                    ri3 += lhs[ 4*j + 3 + lhsOffset ] * rhs_ij;
+                }
+                result[ 4*i + 0 + resultOffset ] = ri0;
+                result[ 4*i + 1 + resultOffset ] = ri1;
+                result[ 4*i + 2 + resultOffset ] = ri2;
+                result[ 4*i + 3 + resultOffset ] = ri3;
+            }
+        }
+    }
 
     /**
      * Multiplies a 4 element vector by a 4x4 matrix and stores the result in a
      * 4-element column vector. In matrix notation: result = lhs x rhs
      * <p>
      * The same float array may be passed for resultVec, lhsMat, and/or rhsVec.
-     * However, the resultVec element values are undefined if the resultVec
-     * elements overlap either the lhsMat or rhsVec elements.
+     * This operation is expected to do the correct thing if the result elements
+     * overlap with either of the lhs or rhs elements.
      *
      * @param resultVec The float array that holds the result vector.
      * @param resultVecOffset The offset into the result array where the result
@@ -89,14 +210,67 @@
      * @param rhsVecOffset The offset into the rhs vector where the rhs vector
      *        is stored.
      *
-     * @throws IllegalArgumentException if resultVec, lhsMat,
-     * or rhsVec are null, or if resultVecOffset + 4 > resultVec.length
-     * or lhsMatOffset + 16 > lhsMat.length or
-     * rhsVecOffset + 4 > rhsVec.length.
+     * @throws IllegalArgumentException under any of the following conditions:
+     * resultVec, lhsMat, or rhsVec are null;
+     * resultVecOffset + 4  > resultVec.length
+     * or lhsMatOffset + 16 > lhsMat.length
+     * or rhsVecOffset + 4  > rhsVec.length;
+     * resultVecOffset < 0 or lhsMatOffset < 0 or rhsVecOffset < 0
      */
-    public static native void multiplyMV(float[] resultVec,
+    public static void multiplyMV(float[] resultVec,
             int resultVecOffset, float[] lhsMat, int lhsMatOffset,
-            float[] rhsVec, int rhsVecOffset);
+            float[] rhsVec, int rhsVecOffset) {
+        // error checking
+        if (resultVec == null) {
+            throw new IllegalArgumentException("resultVec == null");
+        }
+        if (lhsMat == null) {
+            throw new IllegalArgumentException("lhsMat == null");
+        }
+        if (rhsVec == null) {
+            throw new IllegalArgumentException("rhsVec == null");
+        }
+        if (resultVecOffset < 0) {
+            throw new IllegalArgumentException("resultVecOffset < 0");
+        }
+        if (lhsMatOffset < 0) {
+            throw new IllegalArgumentException("lhsMatOffset < 0");
+        }
+        if (rhsVecOffset < 0) {
+            throw new IllegalArgumentException("rhsVecOffset < 0");
+        }
+        if (resultVec.length < resultVecOffset + 4) {
+            throw new IllegalArgumentException("resultVec.length < resultVecOffset + 4");
+        }
+        if (lhsMat.length < lhsMatOffset + 16) {
+            throw new IllegalArgumentException("lhsMat.length < lhsMatOffset + 16");
+        }
+        if (rhsVec.length < rhsVecOffset + 4) {
+            throw new IllegalArgumentException("rhsVec.length < rhsVecOffset + 4");
+        }
+
+        float tmp0 = lhsMat[0 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[0 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[0 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[0 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+        float tmp1 = lhsMat[1 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[1 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[1 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[1 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+        float tmp2 = lhsMat[2 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[2 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[2 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[2 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+        float tmp3 = lhsMat[3 + 4 * 0 + lhsMatOffset] * rhsVec[0 + rhsVecOffset] +
+                     lhsMat[3 + 4 * 1 + lhsMatOffset] * rhsVec[1 + rhsVecOffset] +
+                     lhsMat[3 + 4 * 2 + lhsMatOffset] * rhsVec[2 + rhsVecOffset] +
+                     lhsMat[3 + 4 * 3 + lhsMatOffset] * rhsVec[3 + rhsVecOffset];
+
+        resultVec[ 0 + resultVecOffset ] = tmp0;
+        resultVec[ 1 + resultVecOffset ] = tmp1;
+        resultVec[ 2 + resultVecOffset ] = tmp2;
+        resultVec[ 3 + resultVecOffset ] = tmp3;
+    }
 
     /**
      * Transposes a 4 x 4 matrix.
@@ -537,10 +711,9 @@
     public static void rotateM(float[] rm, int rmOffset,
             float[] m, int mOffset,
             float a, float x, float y, float z) {
-        synchronized(sTemp) {
-            setRotateM(sTemp, 0, a, x, y, z);
-            multiplyMM(rm, rmOffset, m, mOffset, sTemp, 0);
-        }
+        float[] tmp = ThreadTmp.get();
+        setRotateM(tmp, 16, a, x, y, z);
+        multiplyMM(rm, rmOffset, m, mOffset, tmp, 16);
     }
 
     /**
@@ -556,11 +729,7 @@
      */
     public static void rotateM(float[] m, int mOffset,
             float a, float x, float y, float z) {
-        synchronized(sTemp) {
-            setRotateM(sTemp, 0, a, x, y, z);
-            multiplyMM(sTemp, 16, m, mOffset, sTemp, 0);
-            System.arraycopy(sTemp, 16, m, mOffset, 16);
-        }
+        rotateM(m, mOffset, m, mOffset, a, x, y, z);
     }
 
     /**
@@ -640,9 +809,14 @@
      * @param rm returns the result
      * @param rmOffset index into rm where the result matrix starts
      * @param x angle of rotation, in degrees
-     * @param y angle of rotation, in degrees
+     * @param y is broken, do not use
      * @param z angle of rotation, in degrees
+     *
+     * @deprecated This method is incorrect around the y axis. This method is
+     *             deprecated and replaced (below) by setRotateEulerM2 which
+     *             behaves correctly
      */
+    @Deprecated
     public static void setRotateEulerM(float[] rm, int rmOffset,
             float x, float y, float z) {
         x *= (float) (Math.PI / 180.0f);
@@ -679,6 +853,64 @@
     }
 
     /**
+     * Converts Euler angles to a rotation matrix.
+     *
+     * @param rm returns the result
+     * @param rmOffset index into rm where the result matrix starts
+     * @param x angle of rotation, in degrees
+     * @param y angle of rotation, in degrees
+     * @param z angle of rotation, in degrees
+     *
+     * @throws IllegalArgumentException if rm is null;
+     * or if rmOffset + 16 > rm.length;
+     * rmOffset < 0
+     */
+    public static void setRotateEulerM2(@NonNull float[] rm, int rmOffset,
+            float x, float y, float z) {
+        if (rm == null) {
+            throw new IllegalArgumentException("rm == null");
+        }
+        if (rmOffset < 0) {
+            throw new IllegalArgumentException("rmOffset < 0");
+        }
+        if (rm.length < rmOffset + 16) {
+            throw new IllegalArgumentException("rm.length < rmOffset + 16");
+        }
+
+        x *= (float) (Math.PI / 180.0f);
+        y *= (float) (Math.PI / 180.0f);
+        z *= (float) (Math.PI / 180.0f);
+        float cx = (float) Math.cos(x);
+        float sx = (float) Math.sin(x);
+        float cy = (float) Math.cos(y);
+        float sy = (float) Math.sin(y);
+        float cz = (float) Math.cos(z);
+        float sz = (float) Math.sin(z);
+        float cxsy = cx * sy;
+        float sxsy = sx * sy;
+
+        rm[rmOffset + 0]  =  cy * cz;
+        rm[rmOffset + 1]  = -cy * sz;
+        rm[rmOffset + 2]  =  sy;
+        rm[rmOffset + 3]  =  0.0f;
+
+        rm[rmOffset + 4]  =  sxsy * cz + cx * sz;
+        rm[rmOffset + 5]  = -sxsy * sz + cx * cz;
+        rm[rmOffset + 6]  = -sx * cy;
+        rm[rmOffset + 7]  =  0.0f;
+
+        rm[rmOffset + 8]  = -cxsy * cz + sx * sz;
+        rm[rmOffset + 9]  =  cxsy * sz + sx * cz;
+        rm[rmOffset + 10] =  cx * cy;
+        rm[rmOffset + 11] =  0.0f;
+
+        rm[rmOffset + 12] =  0.0f;
+        rm[rmOffset + 13] =  0.0f;
+        rm[rmOffset + 14] =  0.0f;
+        rm[rmOffset + 15] =  1.0f;
+    }
+
+    /**
      * Defines a viewing transformation in terms of an eye point, a center of
      * view, and an up vector.
      *
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 86b4711..3f4f178 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -45,7 +45,7 @@
 import com.android.credentialmanager.createflow.EnabledProviderInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
-import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
+import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toCredentialDataBundle
 import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
 import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 import com.android.credentialmanager.jetpack.provider.Action
@@ -325,7 +325,7 @@
                 key,
                 subkey,
                 CredentialEntry.toSlice(credentialEntry),
-                null
+                Intent()
         )
   }
 
@@ -348,7 +348,7 @@
                 android.service.credentials.CallingAppInfo(
                         context.applicationInfo.packageName, SigningInfo()),
                 TYPE_PASSWORD_CREDENTIAL,
-                toBundle("beckett-bakert@gmail.com", "password123")
+                toCredentialDataBundle("beckett-bakert@gmail.com", "password123")
         )
         val fillInIntent = Intent().putExtra(
                 CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
@@ -417,7 +417,7 @@
             "                     \"residentKey\": \"required\",\n" +
             "                     \"requireResidentKey\": true\n" +
             "                   }}")
-    val credentialData = request.data
+    val credentialData = request.credentialData
     return RequestInfo.newCreateRequestInfo(
       Binder(),
       CreateCredentialRequest(
@@ -432,7 +432,7 @@
   }
 
   private fun testCreatePasswordRequestInfo(): RequestInfo {
-    val data = toBundle("beckett-bakert@gmail.com", "password123")
+    val data = toCredentialDataBundle("beckett-bakert@gmail.com", "password123")
     return RequestInfo.newCreateRequestInfo(
       Binder(),
       CreateCredentialRequest(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 9108f57..3cd4217 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -242,7 +242,7 @@
           packageName = it.providerFlattenedComponentName
         }
         val pkgInfo = packageManager
-                .getPackageInfo(packageName,
+                .getPackageInfo(packageName!!,
                         PackageManager.PackageInfoFlags.of(0))
         DisabledProviderInfo(
                 icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
@@ -264,7 +264,7 @@
       val createCredentialRequest = requestInfo.createCredentialRequest
       val createCredentialRequestJetpack = createCredentialRequest?.let {
         CreateCredentialRequest.createFrom(
-                it
+                it.type, it.credentialData, it.candidateQueryData, it.requireSystemProvider()
         )
       }
       when (createCredentialRequestJetpack) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
index 008e1b6..eaa2ad4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
@@ -18,6 +18,7 @@
 
 import android.credentials.Credential
 import android.os.Bundle
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
 
 /**
  * Base request class for registering a credential.
@@ -28,27 +29,44 @@
  *                              otherwise
  */
 open class CreateCredentialRequest(
-        val type: String,
-        val data: Bundle,
-        val requireSystemProvider: Boolean,
+    open val type: String,
+    open val credentialData: Bundle,
+    open val candidateQueryData: Bundle,
+    open val requireSystemProvider: Boolean
 ) {
     companion object {
         @JvmStatic
-        fun createFrom(from: android.credentials.CreateCredentialRequest): CreateCredentialRequest {
+        fun createFrom(
+            type: String,
+            credentialData: Bundle,
+            candidateQueryData: Bundle,
+            requireSystemProvider: Boolean
+        ): CreateCredentialRequest {
             return try {
-                when (from.type) {
+                when (type) {
                     Credential.TYPE_PASSWORD_CREDENTIAL ->
-                        CreatePasswordRequest.createFrom(from.credentialData)
+                        CreatePasswordRequest.createFrom(credentialData)
                     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
-                        CreatePublicKeyCredentialBaseRequest.createFrom(from.credentialData)
-                    else ->
-                        CreateCredentialRequest(
-                            from.type, from.credentialData, from.requireSystemProvider()
-                        )
+                        when (credentialData.getString(BUNDLE_KEY_SUBTYPE)) {
+                            CreatePublicKeyCredentialRequest
+                                .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
+                                CreatePublicKeyCredentialRequest.createFrom(credentialData)
+                            CreatePublicKeyCredentialRequestPrivileged
+                                .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV ->
+                                CreatePublicKeyCredentialRequestPrivileged
+                                    .createFrom(credentialData)
+                            else -> throw FrameworkClassParsingException()
+                        }
+                    else -> throw FrameworkClassParsingException()
                 }
             } catch (e: FrameworkClassParsingException) {
-                CreateCredentialRequest(
-                    from.type, from.credentialData, from.requireSystemProvider()
+                // Parsing failed but don't crash the process. Instead just output a request with
+                // the raw framework values.
+                CreateCustomCredentialRequest(
+                    type,
+                    credentialData,
+                    candidateQueryData,
+                    requireSystemProvider
                 )
             }
         }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt
new file mode 100644
index 0000000..50da9a1
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCustomCredentialRequest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Base custom create request class for registering a credential.
+ *
+ * An application can construct a subtype custom request and call
+ * [CredentialManager.executeCreateCredential] to launch framework UI flows to collect consent and
+ * any other metadata needed from the user to register a new user credential.
+ *
+ * @property type the credential type determined by the credential-type-specific subclass for custom
+ * use cases
+ * @property credentialData the full credential creation request data in the [Bundle] format for
+ * custom use cases
+ * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to
+ * the provider during the initial candidate query stage, which should not contain sensitive user
+ * credential information
+ * @property requireSystemProvider true if must only be fulfilled by a system provider and false
+ * otherwise
+ * @throws IllegalArgumentException If [type] is empty
+ * @throws NullPointerException If [type] or [credentialData] are null
+ */
+open class CreateCustomCredentialRequest(
+    final override val type: String,
+    final override val credentialData: Bundle,
+    final override val candidateQueryData: Bundle,
+    @get:JvmName("requireSystemProvider")
+    final override val requireSystemProvider: Boolean
+) : CreateCredentialRequest(type, credentialData, candidateQueryData, requireSystemProvider) {
+    init {
+        require(type.isNotEmpty()) { "type should not be empty" }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
index f0da9f9..bf0aa8a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
@@ -32,9 +32,11 @@
         val id: String,
         val password: String,
 ) : CreateCredentialRequest(
-        Credential.TYPE_PASSWORD_CREDENTIAL,
-        toBundle(id, password),
-        false,
+        type = Credential.TYPE_PASSWORD_CREDENTIAL,
+        credentialData = toCredentialDataBundle(id, password),
+        // No credential data should be sent during the query phase.
+        candidateQueryData = Bundle(),
+        requireSystemProvider = false,
 ) {
 
     init {
@@ -46,7 +48,7 @@
         const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"
 
         @JvmStatic
-        internal fun toBundle(id: String, password: String): Bundle {
+        internal fun toCredentialDataBundle(id: String, password: String): Bundle {
             val bundle = Bundle()
             bundle.putString(BUNDLE_KEY_ID, id)
             bundle.putString(BUNDLE_KEY_PASSWORD, password)
@@ -54,7 +56,14 @@
         }
 
         @JvmStatic
-        fun createFrom(data: Bundle): CreatePasswordRequest {
+        internal fun toCandidateDataBundle(id: String): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_ID, id)
+            return bundle
+        }
+
+        @JvmStatic
+        internal fun createFrom(data: Bundle): CreatePasswordRequest {
             try {
                 val id = data.getString(BUNDLE_KEY_ID)
                 val password = data.getString(BUNDLE_KEY_PASSWORD)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
deleted file mode 100644
index 37a4f76..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * Base request class for registering a public key credential.
- *
- * @property requestJson The request in JSON format
- * @throws NullPointerException If [requestJson] is null. This is handled by the Kotlin runtime
- * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
- */
-abstract class CreatePublicKeyCredentialBaseRequest constructor(
-        val requestJson: String,
-        type: String,
-        data: Bundle,
-        requireSystemProvider: Boolean,
-) : CreateCredentialRequest(type, data, requireSystemProvider) {
-
-    init {
-        require(requestJson.isNotEmpty()) { "request json must not be empty" }
-    }
-
-    companion object {
-        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-        const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
-
-        @JvmStatic
-        fun createFrom(data: Bundle): CreatePublicKeyCredentialBaseRequest {
-            return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
-                CreatePublicKeyCredentialRequest
-                        .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
-                    CreatePublicKeyCredentialRequest.createFrom(data)
-                CreatePublicKeyCredentialRequestPrivileged
-                        .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED ->
-                    CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
-                else -> throw FrameworkClassParsingException()
-            }
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
index 2eda90b..f3d402a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
@@ -17,50 +17,81 @@
 package com.android.credentialmanager.jetpack.developer
 
 import android.os.Bundle
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
 
 /**
- * A request to register a passkey from the user's public key credential provider.
- *
- * @property requestJson the request in JSON format
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @throws NullPointerException If [requestJson] or [allowHybrid] is null. This is handled by the
- * Kotlin runtime
- * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
- */
+* A request to register a passkey from the user's public key credential provider.
+*
+* @property requestJson the privileged request in JSON format in the standard webauthn web json
+* shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
+* @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+* immediately when there is no available passkey registration offering instead of falling back to
+* discovering remote options, and false (default) otherwise
+* @throws NullPointerException If [requestJson] is null
+* @throws IllegalArgumentException If [requestJson] is empty
+*/
 class CreatePublicKeyCredentialRequest @JvmOverloads constructor(
-        requestJson: String,
-        @get:JvmName("allowHybrid")
-        val allowHybrid: Boolean = true
-) : CreatePublicKeyCredentialBaseRequest(
-        requestJson,
-        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-        toBundle(requestJson, allowHybrid),
-        false,
+    val requestJson: String,
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean = false
+) : CreateCredentialRequest(
+    type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+    credentialData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials),
+    // The whole request data should be passed during the query phase.
+    candidateQueryData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials),
+    requireSystemProvider = false,
 ) {
+
+    init {
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+    }
+
+    /** @hide */
     companion object {
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
-        const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST =
-                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST"
+        const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
+        internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+        internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST =
+            "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST"
 
         @JvmStatic
-        internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+        internal fun toCredentialDataBundle(
+            requestJson: String,
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Bundle {
             val bundle = Bundle()
             bundle.putString(BUNDLE_KEY_SUBTYPE,
-                    BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
+                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials)
             return bundle
         }
 
         @JvmStatic
-        fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest {
+        internal fun toCandidateDataBundle(
+            requestJson: String,
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_SUBTYPE,
+                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
+            bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+            bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials)
+            return bundle
+        }
+
+        @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
+        // boolean value from being returned.
+        @JvmStatic
+        internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
-                return CreatePublicKeyCredentialRequest(requestJson!!, (allowHybrid!!) as Boolean)
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
+                return CreatePublicKeyCredentialRequest(requestJson!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean)
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
index 36324f8..85ab2d2 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
@@ -17,45 +17,62 @@
 package com.android.credentialmanager.jetpack.developer
 
 import android.os.Bundle
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
 
 /**
  * A privileged request to register a passkey from the user’s public key credential provider, where
  * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default
- * brower, caBLE, can use this.
+ * brower, caBLE, can use this. These permissions will be introduced in an upcoming release.
+ * TODO("Add specific permission info/annotation")
  *
- * @property requestJson the privileged request in JSON format
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @property rp the expected true RP ID which will override the one in the [requestJson]
- * @property clientDataHash a hash that is used to verify the [rp] Identity
- * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] is
- * null. This is handled by the Kotlin runtime
- * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
- *
- * @hide
+ * @property requestJson the privileged request in JSON format in the standard webauthn web json
+ * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
+ * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+ * immediately when there is no available passkey registration offering instead of falling back to
+ * discovering remote options, and false (default) otherwise
+ * @property relyingParty the expected true RP ID which will override the one in the [requestJson],
+ * where rp is defined [here](https://w3c.github.io/webauthn/#rp-id)
+ * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
+ * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] is
+ * null
+ * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is
+ * empty
  */
 class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor(
-        requestJson: String,
-        val rp: String,
-        val clientDataHash: String,
-        @get:JvmName("allowHybrid")
-        val allowHybrid: Boolean = true
-) : CreatePublicKeyCredentialBaseRequest(
+    val requestJson: String,
+    val relyingParty: String,
+    val clientDataHash: String,
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean = false
+) : CreateCredentialRequest(
+    type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+    credentialData = toCredentialDataBundle(
         requestJson,
-        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-        toBundle(requestJson, rp, clientDataHash, allowHybrid),
-        false,
+        relyingParty,
+        clientDataHash,
+        preferImmediatelyAvailableCredentials
+    ),
+    // The whole request data should be passed during the query phase.
+    candidateQueryData = toCredentialDataBundle(
+        requestJson, relyingParty, clientDataHash, preferImmediatelyAvailableCredentials
+    ),
+    requireSystemProvider = false,
 ) {
 
     init {
-        require(rp.isNotEmpty()) { "rp must not be empty" }
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+        require(relyingParty.isNotEmpty()) { "rp must not be empty" }
         require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
     }
 
     /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */
-    class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+    class Builder(
+        private var requestJson: String,
+        private var relyingParty: String,
+        private var clientDataHash: String
+    ) {
 
-        private var allowHybrid: Boolean = true
+        private var preferImmediatelyAvailableCredentials: Boolean = false
 
         /**
          * Sets the privileged request in JSON format.
@@ -66,23 +83,30 @@
         }
 
         /**
-         * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+         * Sets to true if you prefer the operation to return immediately when there is no available
+         * passkey registration offering instead of falling back to discovering remote options, and
+         * false otherwise.
+         *
+         * The default value is false.
          */
-        fun setAllowHybrid(allowHybrid: Boolean): Builder {
-            this.allowHybrid = allowHybrid
+        @Suppress("MissingGetterMatchingBuilder")
+        fun setPreferImmediatelyAvailableCredentials(
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Builder {
+            this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
             return this
         }
 
         /**
          * Sets the expected true RP ID which will override the one in the [requestJson].
          */
-        fun setRp(rp: String): Builder {
-            this.rp = rp
+        fun setRelyingParty(relyingParty: String): Builder {
+            this.relyingParty = relyingParty
             return this
         }
 
         /**
-         * Sets a hash that is used to verify the [rp] Identity.
+         * Sets a hash that is used to verify the [relyingParty] Identity.
          */
         fun setClientDataHash(clientDataHash: String): Builder {
             this.clientDataHash = clientDataHash
@@ -91,49 +115,65 @@
 
         /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */
         fun build(): CreatePublicKeyCredentialRequestPrivileged {
-            return CreatePublicKeyCredentialRequestPrivileged(this.requestJson,
-                    this.rp, this.clientDataHash, this.allowHybrid)
+            return CreatePublicKeyCredentialRequestPrivileged(
+                this.requestJson,
+                this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials
+            )
         }
     }
 
+    /** @hide */
     companion object {
-        const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
-        const val BUNDLE_KEY_CLIENT_DATA_HASH =
-                "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
-        const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED =
-                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" +
-                        "PRIVILEGED"
+        internal const val BUNDLE_KEY_RELYING_PARTY =
+            "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
+        internal const val BUNDLE_KEY_CLIENT_DATA_HASH =
+            "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
+        internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
+
+        internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+
+        internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV =
+            "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" +
+                    "PRIVILEGED"
 
         @JvmStatic
-        internal fun toBundle(
-                requestJson: String,
-                rp: String,
-                clientDataHash: String,
-                allowHybrid: Boolean
+        internal fun toCredentialDataBundle(
+            requestJson: String,
+            relyingParty: String,
+            clientDataHash: String,
+            preferImmediatelyAvailableCredentials: Boolean
         ): Bundle {
             val bundle = Bundle()
-            bundle.putString(BUNDLE_KEY_SUBTYPE,
-                    BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED)
+            bundle.putString(
+                PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
+                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV
+            )
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putString(BUNDLE_KEY_RP, rp)
+            bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
             bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials
+            )
             return bundle
         }
 
+        @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
+        // boolean value from being returned.
         @JvmStatic
-        fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged {
+        internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
-                val rp = data.getString(BUNDLE_KEY_RP)
+                val rp = data.getString(BUNDLE_KEY_RELYING_PARTY)
                 val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
                 return CreatePublicKeyCredentialRequestPrivileged(
-                        requestJson!!,
-                        rp!!,
-                        clientDataHash!!,
-                        (allowHybrid!!) as Boolean,
+                    requestJson!!,
+                    rp!!,
+                    clientDataHash!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean,
                 )
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
index ef48a77..fc7b7de 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
@@ -28,30 +28,40 @@
  *                              otherwise
  */
 open class GetCredentialOption(
-    val type: String,
-    val data: Bundle,
-    val requireSystemProvider: Boolean,
+    open val type: String,
+    open val requestData: Bundle,
+    open val candidateQueryData: Bundle,
+    open val requireSystemProvider: Boolean,
 ) {
     companion object {
         @JvmStatic
-        fun createFrom(from: android.credentials.GetCredentialOption): GetCredentialOption {
+        fun createFrom(
+            type: String,
+            requestData: Bundle,
+            candidateQueryData: Bundle,
+            requireSystemProvider: Boolean
+        ): GetCredentialOption {
             return try {
-                when (from.type) {
+                when (type) {
                     Credential.TYPE_PASSWORD_CREDENTIAL ->
-                        GetPasswordOption.createFrom(from.credentialRetrievalData)
+                        GetPasswordOption.createFrom(requestData)
                     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
-                        GetPublicKeyCredentialBaseOption.createFrom(from.credentialRetrievalData)
-                    else ->
-                        GetCredentialOption(
-                            from.type, from.credentialRetrievalData, from.requireSystemProvider()
-                        )
+                        when (requestData.getString(PublicKeyCredential.BUNDLE_KEY_SUBTYPE)) {
+                            GetPublicKeyCredentialOption
+                                .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION ->
+                                GetPublicKeyCredentialOption.createFrom(requestData)
+                            GetPublicKeyCredentialOptionPrivileged
+                                .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED ->
+                                GetPublicKeyCredentialOptionPrivileged.createFrom(requestData)
+                            else -> throw FrameworkClassParsingException()
+                        }
+                    else -> throw FrameworkClassParsingException()
                 }
             } catch (e: FrameworkClassParsingException) {
-                GetCredentialOption(
-                    from.type,
-                    from.credentialRetrievalData,
-                    from.requireSystemProvider()
-                )
+                // Parsing failed but don't crash the process. Instead just output a request with
+                // the raw framework values.
+                GetCustomCredentialOption(
+                    type, requestData, candidateQueryData, requireSystemProvider)
             }
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
index 7f9256e..18d5089 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -24,7 +24,7 @@
  * @throws IllegalArgumentException If [getCredentialOptions] is empty
  */
 class GetCredentialRequest constructor(
-        val getCredentialOptions: List<GetCredentialOption>,
+    val getCredentialOptions: List<GetCredentialOption>,
 ) {
 
     init {
@@ -61,7 +61,14 @@
         @JvmStatic
         fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest {
             return GetCredentialRequest(
-                    from.getCredentialOptions.map {GetCredentialOption.createFrom(it)}
+                from.getCredentialOptions.map {
+                    GetCredentialOption.createFrom(
+                        it.type,
+                        it.credentialRetrievalData,
+                        it.candidateQueryData,
+                        it.requireSystemProvider()
+                    )
+                }
             )
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt
new file mode 100644
index 0000000..803885c
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCustomCredentialOption.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Allows extending custom versions of GetCredentialOptions for unique use cases.
+ *
+ * @property type the credential type determined by the credential-type-specific subclass
+ * generated for custom use cases
+ * @property requestData the request data in the [Bundle] format, generated for custom use cases
+ * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to
+ * the provider during the initial candidate query stage, which should not contain sensitive user
+ * information
+ * @property requireSystemProvider true if must only be fulfilled by a system provider and false
+ * otherwise
+ * @throws IllegalArgumentException If [type] is empty
+ * @throws NullPointerException If [requestData] or [type] is null
+ */
+open class GetCustomCredentialOption(
+    final override val type: String,
+    final override val requestData: Bundle,
+    final override val candidateQueryData: Bundle,
+    @get:JvmName("requireSystemProvider")
+    final override val requireSystemProvider: Boolean
+) : GetCredentialOption(
+    type,
+    requestData,
+    candidateQueryData,
+    requireSystemProvider
+) {
+    init {
+        require(type.isNotEmpty()) { "type should not be empty" }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
index 2facad1..2b9cfa3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
@@ -23,6 +23,7 @@
 class GetPasswordOption : GetCredentialOption(
         Credential.TYPE_PASSWORD_CREDENTIAL,
         Bundle(),
+        Bundle(),
         false,
 ) {
     companion object {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt
deleted file mode 100644
index 9b51b30..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.credentialmanager.jetpack.developer
-
-import android.os.Bundle
-
-/**
- * Base request class for getting a registered public key credential.
- *
- * @property requestJson the request in JSON format
- * @throws NullPointerException If [requestJson] is null - auto handled by the
- * Kotlin runtime
- * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
- */
-abstract class GetPublicKeyCredentialBaseOption constructor(
-        val requestJson: String,
-        type: String,
-        data: Bundle,
-        requireSystemProvider: Boolean,
-) : GetCredentialOption(type, data, requireSystemProvider) {
-
-    init {
-        require(requestJson.isNotEmpty()) { "request json must not be empty" }
-    }
-
-    companion object {
-        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-        const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
-
-        @JvmStatic
-        fun createFrom(data: Bundle): GetPublicKeyCredentialBaseOption {
-            return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
-                GetPublicKeyCredentialOption
-                        .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION ->
-                    GetPublicKeyCredentialOption.createFrom(data)
-                GetPublicKeyCredentialOptionPrivileged
-                        .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED ->
-                    GetPublicKeyCredentialOptionPrivileged.createFrom(data)
-                else -> throw FrameworkClassParsingException()
-            }
-        }
-    }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
index 6f13c17..2f9b249 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
@@ -21,44 +21,62 @@
 /**
  * A request to get passkeys from the user's public key credential provider.
  *
- * @property requestJson the request in JSON format
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @throws NullPointerException If [requestJson] or [allowHybrid] is null. It is handled by the
- * Kotlin runtime
+ * @property requestJson the privileged request in JSON format in the standard webauthn web json
+ * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
+ * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+ * immediately when there is no available credential instead of falling back to discovering remote
+ * credentials, and false (default) otherwise
+ * @throws NullPointerException If [requestJson] is null
  * @throws IllegalArgumentException If [requestJson] is empty
- *
- * @hide
  */
 class GetPublicKeyCredentialOption @JvmOverloads constructor(
-        requestJson: String,
-        @get:JvmName("allowHybrid")
-        val allowHybrid: Boolean = true,
-) : GetPublicKeyCredentialBaseOption(
-        requestJson,
-        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-        toBundle(requestJson, allowHybrid),
-        false
+    val requestJson: String,
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean = false,
+) : GetCredentialOption(
+    type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+    requestData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials),
+    candidateQueryData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials),
+    requireSystemProvider = false
 ) {
+    init {
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+    }
+
+    /** @hide */
     companion object {
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
-        const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
-                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION"
+        internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
+        internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+        internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
+            "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION"
 
         @JvmStatic
-        internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+        internal fun toRequestDataBundle(
+            requestJson: String,
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Bundle {
             val bundle = Bundle()
+            bundle.putString(
+                PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
+                BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION
+            )
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials)
             return bundle
         }
 
+        @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
+        // boolean value from being returned.
         @JvmStatic
-        fun createFrom(data: Bundle): GetPublicKeyCredentialOption {
+        internal fun createFrom(data: Bundle): GetPublicKeyCredentialOption {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
-                return GetPublicKeyCredentialOption(requestJson!!, (allowHybrid!!) as Boolean)
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
+                return GetPublicKeyCredentialOption(requestJson!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean)
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
             }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
index 79c62a1..6f4782a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
@@ -21,41 +21,59 @@
 /**
  * A privileged request to get passkeys from the user's public key credential provider. The caller
  * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE)
- * can use this.
+ * can use this. These permissions will be introduced in an upcoming release.
+ * TODO("Add specific permission info/annotation")
  *
- * @property requestJson the privileged request in JSON format
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default
- * @property rp the expected true RP ID which will override the one in the [requestJson]
- * @property clientDataHash a hash that is used to verify the [rp] Identity
- * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash]
- * is null. This is handled by the Kotlin runtime
- * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
- *
- * @hide
+ * @property requestJson the privileged request in JSON format in the standard webauthn web json
+ * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
+ * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+ * immediately when there is no available credential instead of falling back to discovering remote
+ * credentials, and false (default) otherwise
+ * @property relyingParty the expected true RP ID which will override the one in the [requestJson],
+ * where relyingParty is defined [here](https://w3c.github.io/webauthn/#rp-id) in more detail
+ * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
+ * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash]
+ * is null
+ * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is
+ * empty
  */
 class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor(
-        requestJson: String,
-        val rp: String,
-        val clientDataHash: String,
-        @get:JvmName("allowHybrid")
-        val allowHybrid: Boolean = true
-) : GetPublicKeyCredentialBaseOption(
+    val requestJson: String,
+    val relyingParty: String,
+    val clientDataHash: String,
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean = false
+) : GetCredentialOption(
+    type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+    requestData = toBundle(
         requestJson,
-        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-        toBundle(requestJson, rp, clientDataHash, allowHybrid),
-        false,
+        relyingParty,
+        clientDataHash,
+        preferImmediatelyAvailableCredentials
+    ),
+    candidateQueryData = toBundle(
+        requestJson,
+        relyingParty,
+        clientDataHash,
+        preferImmediatelyAvailableCredentials
+    ),
+    requireSystemProvider = false,
 ) {
 
     init {
-        require(rp.isNotEmpty()) { "rp must not be empty" }
+        require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
+        require(relyingParty.isNotEmpty()) { "rp must not be empty" }
         require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
     }
 
     /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */
-    class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+    class Builder(
+        private var requestJson: String,
+        private var relyingParty: String,
+        private var clientDataHash: String
+    ) {
 
-        private var allowHybrid: Boolean = true
+        private var preferImmediatelyAvailableCredentials: Boolean = false
 
         /**
          * Sets the privileged request in JSON format.
@@ -66,23 +84,30 @@
         }
 
         /**
-         * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+         * Sets to true if you prefer the operation to return immediately when there is no available
+         * credential instead of falling back to discovering remote credentials, and false
+         * otherwise.
+         *
+         * The default value is false.
          */
-        fun setAllowHybrid(allowHybrid: Boolean): Builder {
-            this.allowHybrid = allowHybrid
+        @Suppress("MissingGetterMatchingBuilder")
+        fun setPreferImmediatelyAvailableCredentials(
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Builder {
+            this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
             return this
         }
 
         /**
          * Sets the expected true RP ID which will override the one in the [requestJson].
          */
-        fun setRp(rp: String): Builder {
-            this.rp = rp
+        fun setRelyingParty(relyingParty: String): Builder {
+            this.relyingParty = relyingParty
             return this
         }
 
         /**
-         * Sets a hash that is used to verify the [rp] Identity.
+         * Sets a hash that is used to verify the [relyingParty] Identity.
          */
         fun setClientDataHash(clientDataHash: String): Builder {
             this.clientDataHash = clientDataHash
@@ -91,47 +116,63 @@
 
         /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */
         fun build(): GetPublicKeyCredentialOptionPrivileged {
-            return GetPublicKeyCredentialOptionPrivileged(this.requestJson,
-                    this.rp, this.clientDataHash, this.allowHybrid)
+            return GetPublicKeyCredentialOptionPrivileged(
+                this.requestJson,
+                this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials
+            )
         }
     }
 
+    /** @hide */
     companion object {
-        const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
-        const val BUNDLE_KEY_CLIENT_DATA_HASH =
-                "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
-        const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED =
-                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" +
-                        "_PRIVILEGED"
+        internal const val BUNDLE_KEY_RELYING_PARTY =
+            "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
+        internal const val BUNDLE_KEY_CLIENT_DATA_HASH =
+            "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
+        internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
+        internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+        internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED =
+            "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" +
+                    "_PRIVILEGED"
 
         @JvmStatic
         internal fun toBundle(
-                requestJson: String,
-                rp: String,
-                clientDataHash: String,
-                allowHybrid: Boolean
+            requestJson: String,
+            relyingParty: String,
+            clientDataHash: String,
+            preferImmediatelyAvailableCredentials: Boolean
         ): Bundle {
             val bundle = Bundle()
+            bundle.putString(
+                PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
+                BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED
+            )
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putString(BUNDLE_KEY_RP, rp)
+            bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
             bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials
+            )
             return bundle
         }
 
+        @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
+        // boolean value from being returned.
         @JvmStatic
-        fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged {
+        internal fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
-                val rp = data.getString(BUNDLE_KEY_RP)
+                val rp = data.getString(BUNDLE_KEY_RELYING_PARTY)
                 val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
                 return GetPublicKeyCredentialOptionPrivileged(
-                        requestJson!!,
-                        rp!!,
-                        clientDataHash!!,
-                        (allowHybrid!!) as Boolean,
+                    requestJson!!,
+                    rp!!,
+                    clientDataHash!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean,
                 )
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
index b45a63b..6a81167 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
@@ -45,6 +45,8 @@
         /** The type value for public key credential related operations. */
         const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
                 "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
+        /** The Bundle key value for the public key credential subtype (privileged or regular). */
+        internal const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
         const val BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON =
                 "androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON"
 
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 80f02b4..96a11ee 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -37,7 +37,7 @@
  */
 public class RestrictedLockUtils {
     /**
-     * Get EnforcedAdmin from DevicePolicyManager
+     * Gets EnforcedAdmin from DevicePolicyManager
      */
     @RequiresApi(Build.VERSION_CODES.M)
     public static EnforcedAdmin getProfileOrDeviceOwner(Context context, UserHandle user) {
@@ -45,7 +45,7 @@
     }
 
     /**
-     * Get EnforcedAdmin from DevicePolicyManager
+     * Gets EnforcedAdmin from DevicePolicyManager
      */
     @RequiresApi(Build.VERSION_CODES.M)
     public static EnforcedAdmin getProfileOrDeviceOwner(
@@ -81,7 +81,7 @@
     }
 
     /**
-     * Send the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+     * Sends the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
      */
     @RequiresApi(Build.VERSION_CODES.M)
     public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
@@ -97,6 +97,9 @@
         context.startActivityAsUser(intent, UserHandle.of(targetUserId));
     }
 
+    /**
+     * Gets the intent to trigger the {@code android.settings.ShowAdminSupportDetailsDialog}.
+     */
     public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
         final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
         if (admin != null) {
@@ -109,7 +112,27 @@
     }
 
     /**
-     * Check if current user is profile or not
+     * Shows restricted setting dialog.
+     */
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public static void sendShowRestrictedSettingDialogIntent(Context context,
+            String packageName, int uid) {
+        final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
+        context.startActivity(intent);
+    }
+
+    /**
+     * Gets restricted settings dialog intent.
+     */
+    private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
+        final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        return intent;
+    }
+
+    /**
+     * Checks if current user is profile or not
      */
     @RequiresApi(Build.VERSION_CODES.M)
     public static boolean isCurrentUserOrProfile(Context context, int userId) {
@@ -117,6 +140,9 @@
         return um.getUserProfiles().contains(UserHandle.of(userId));
     }
 
+    /**
+     * A admin for the restriction enforced.
+     */
     public static class EnforcedAdmin {
         @Nullable
         public ComponentName component = null;
@@ -129,12 +155,17 @@
         @Nullable
         public UserHandle user = null;
 
-        // We use this to represent the case where a policy is enforced by multiple admins.
-        public final static EnforcedAdmin MULTIPLE_ENFORCED_ADMIN = new EnforcedAdmin();
+        /**
+         * We use this to represent the case where a policy is enforced by multiple admins.
+         */
+        public static final EnforcedAdmin MULTIPLE_ENFORCED_ADMIN = new EnforcedAdmin();
 
+        /**
+         * The restriction enforced by admin with restriction.
+         */
         public static EnforcedAdmin createDefaultEnforcedAdminWithRestriction(
                 String enforcedRestriction) {
-            EnforcedAdmin enforcedAdmin = new EnforcedAdmin();
+            final EnforcedAdmin enforcedAdmin = new EnforcedAdmin();
             enforcedAdmin.enforcedRestriction = enforcedRestriction;
             return enforcedAdmin;
         }
@@ -159,8 +190,7 @@
             this.user = other.user;
         }
 
-        public EnforcedAdmin() {
-        }
+        public EnforcedAdmin() {}
 
         /**
          * Combines two {@link EnforcedAdmin} into one: if one of them is null, then just return
@@ -189,9 +219,9 @@
             if (this == o) return true;
             if (o == null || getClass() != o.getClass()) return false;
             EnforcedAdmin that = (EnforcedAdmin) o;
-            return Objects.equals(user, that.user) &&
-                    Objects.equals(component, that.component) &&
-                    Objects.equals(enforcedRestriction, that.enforcedRestriction);
+            return Objects.equals(user, that.user)
+                    && Objects.equals(component, that.component)
+                    && Objects.equals(enforcedRestriction, that.enforcedRestriction);
         }
 
         @Override
@@ -201,11 +231,11 @@
 
         @Override
         public String toString() {
-            return "EnforcedAdmin{" +
-                    "component=" + component +
-                    ", enforcedRestriction='" + enforcedRestriction +
-                    ", user=" + user +
-                    '}';
+            return "EnforcedAdmin{"
+                    + "component=" + component
+                    + ", enforcedRestriction='" + enforcedRestriction
+                    + ", user=" + user
+                    + '}';
         }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 5610ac4..78b7810 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -27,7 +27,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -37,7 +36,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.ForegroundColorSpan;
@@ -754,26 +752,6 @@
     }
 
     /**
-     * Show restricted setting dialog.
-     */
-    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    public static void sendShowRestrictedSettingDialogIntent(Context context,
-            String packageName, int uid) {
-        final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
-        context.startActivity(intent);
-    }
-
-    /**
-     * Get restricted settings dialog intent.
-     */
-    private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
-        final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
-        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
-        intent.putExtra(Intent.EXTRA_UID, uid);
-        return intent;
-    }
-
-    /**
      * Static {@link LockPatternUtils} and {@link DevicePolicyManager} wrapper for testing purposes.
      * {@link LockPatternUtils} is an internal API not supported by robolectric.
      * {@link DevicePolicyManager} has a {@code getProfileParent} not yet suppored by robolectric.
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index aa6aaaf..ae06193 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -97,6 +97,7 @@
         Settings.Global.Wearable.GESTURE_TOUCH_AND_HOLD_WATCH_FACE_ENABLED,
         Settings.Global.Wearable.BATTERY_SAVER_MODE,
         Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MS,
-        Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER
+        Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER,
+        Settings.Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 2b0d837..a1a9e8c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -352,5 +352,6 @@
                                 String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SKIPPED),
                                 String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_ABORTED),
                           }));
+        VALIDATORS.put(Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 7073f6a..7f45e5e 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -440,6 +440,11 @@
          This name is in the ComponentName flattened format (package/class)  -->
     <string name="config_screenshotEditor" translatable="false"></string>
 
+    <!-- ComponentName for the file browsing app that the system would expect to be used in work
+         profile. The icon for this app will be shown to the user when informing them that a
+         screenshot has been saved to work profile. If blank, a default icon will be shown. -->
+    <string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string>
+
     <!-- Remote copy default activity.  Must handle REMOTE_COPY_ACTION intents.
      This name is in the ComponentName flattened format (package/class)  -->
     <string name="config_remoteCopyPackage" translatable="false"></string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 45147ca..61a6e9d5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -240,7 +240,9 @@
     <!-- Content description for the right boundary of the screenshot being cropped, with the current position as a percentage. [CHAR LIMIT=NONE] -->
     <string name="screenshot_right_boundary_pct">Right boundary <xliff:g id="percent" example="50">%1$d</xliff:g> percent</string>
     <!-- Notification displayed when a screenshot is saved in a work profile. [CHAR LIMIT=NONE] -->
-    <string name="screenshot_work_profile_notification" translatable="false">Work screenshots are saved in the work <xliff:g id="app" example="Files">%1$s</xliff:g> app</string>
+    <string name="screenshot_work_profile_notification">Work screenshots are saved in the <xliff:g id="app" example="Work Files">%1$s</xliff:g> app</string>
+    <!-- Default name referring to the app on the device that lets the user browse stored files. [CHAR LIMIT=NONE] -->
+    <string name="screenshot_default_files_app_name">Files</string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
     <string name="screenrecord_name">Screen Recorder</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index aec3063..b53b868 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,7 @@
 import android.util.Slog;
 
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockAnimations;
@@ -62,14 +63,16 @@
             ConfigurationController configurationController,
             DozeParameters dozeParameters,
             FeatureFlags featureFlags,
-            ScreenOffAnimationController screenOffAnimationController) {
+            ScreenOffAnimationController screenOffAnimationController,
+            KeyguardLogger logger) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
         mKeyguardClockSwitchController = keyguardClockSwitchController;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mConfigurationController = configurationController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
-                dozeParameters, screenOffAnimationController, /* animateYPos= */ true);
+                dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
+                logger.getBuffer());
         mKeyguardVisibilityHelper.setOcclusionTransitionFlagEnabled(
                 featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION));
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 271fc7b..4e10bff 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -140,6 +140,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -169,6 +170,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
@@ -329,13 +331,11 @@
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
     private ContentObserver mDeviceProvisionedObserver;
-    private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
     private final ContentObserver mTimeFormatChangeObserver;
 
     private boolean mSwitchingUser;
 
     private boolean mDeviceInteractive;
-    private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyListenerManager mTelephonyListenerManager;
     private final TrustManager mTrustManager;
@@ -368,6 +368,7 @@
     private boolean mLogoutEnabled;
     private boolean mIsFaceEnrolled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try. This should
@@ -2039,7 +2040,8 @@
             @Nullable FaceManager faceManager,
             @Nullable FingerprintManager fingerprintManager,
             @Nullable BiometricManager biometricManager,
-            FaceWakeUpTriggersConfig faceWakeUpTriggersConfig) {
+            FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
+            Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2303,30 +2305,7 @@
                 Settings.System.getUriFor(Settings.System.TIME_12_24),
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
 
-        updateSfpsRequireScreenOnToAuthPref();
-        mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                updateSfpsRequireScreenOnToAuthPref();
-            }
-        };
-
-        mContext.getContentResolver().registerContentObserver(
-                mSecureSettings.getUriFor(
-                        Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
-                false,
-                mSfpsRequireScreenOnToAuthPrefObserver,
-                getCurrentUser());
-    }
-
-    protected void updateSfpsRequireScreenOnToAuthPref() {
-        final int defaultSfpsRequireScreenOnToAuthValue =
-                mContext.getResources().getBoolean(
-                        com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
-        mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
-                Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
-                defaultSfpsRequireScreenOnToAuthValue,
-                getCurrentUser()) != 0;
+        mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
     }
 
     private void initializeSimState() {
@@ -2721,8 +2700,11 @@
 
         boolean shouldListenSideFpsState = true;
         if (isSideFps) {
+            final boolean interactiveToAuthEnabled =
+                    mFingerprintInteractiveToAuthProvider != null &&
+                            mFingerprintInteractiveToAuthProvider.isEnabled(getCurrentUser());
             shouldListenSideFpsState =
-                    mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+                    interactiveToAuthEnabled ? isDeviceInteractive() : true;
         }
 
         boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
@@ -3837,11 +3819,6 @@
             mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
         }
 
-        if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
-            mContext.getContentResolver().unregisterContentObserver(
-                    mSfpsRequireScreenOnToAuthPrefObserver);
-        }
-
         try {
             ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
         } catch (RemoteException e) {
@@ -3919,8 +3896,12 @@
                 pw.println("        sfpsEnrolled=" + isSfpsEnrolled());
                 pw.println("        shouldListenForSfps=" + shouldListenForFingerprint(false));
                 if (isSfpsEnrolled()) {
-                    pw.println("        mSfpsRequireScreenOnToAuthPrefEnabled="
-                        + mSfpsRequireScreenOnToAuthPrefEnabled);
+                    final boolean interactiveToAuthEnabled =
+                                    mFingerprintInteractiveToAuthProvider != null &&
+                                            mFingerprintInteractiveToAuthProvider
+                                            .isEnabled(getCurrentUser());
+                    pw.println("        interactiveToAuthEnabled="
+                            + interactiveToAuthEnabled);
                 }
             }
             new DumpsysTableLogger(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index bde0692..7e48193 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -22,6 +22,8 @@
 import android.view.ViewPropertyAnimator;
 
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -31,11 +33,14 @@
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
+import com.google.errorprone.annotations.CompileTimeConstant;
+
 /**
  * Helper class for updating visibility of keyguard views based on keyguard and status bar state.
  * This logic is shared by both the keyguard status view and the keyguard user switcher.
  */
 public class KeyguardVisibilityHelper {
+    private static final String TAG = "KeyguardVisibilityHelper";
 
     private View mView;
     private final KeyguardStateController mKeyguardStateController;
@@ -46,17 +51,26 @@
     private boolean mLastOccludedState = false;
     private boolean mIsUnoccludeTransitionFlagEnabled = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
+    private final LogBuffer mLogBuffer;
 
     public KeyguardVisibilityHelper(View view,
             KeyguardStateController keyguardStateController,
             DozeParameters dozeParameters,
             ScreenOffAnimationController screenOffAnimationController,
-            boolean animateYPos) {
+            boolean animateYPos,
+            LogBuffer logBuffer) {
         mView = view;
         mKeyguardStateController = keyguardStateController;
         mDozeParameters = dozeParameters;
         mScreenOffAnimationController = screenOffAnimationController;
         mAnimateYPos = animateYPos;
+        mLogBuffer = logBuffer;
+    }
+
+    private void log(@CompileTimeConstant String message) {
+        if (mLogBuffer != null) {
+            mLogBuffer.log(TAG, LogLevel.DEBUG, message);
+        }
     }
 
     public boolean isVisibilityAnimating() {
@@ -94,6 +108,9 @@
                         .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
                         .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
                         .start();
+                log("goingToFullShade && keyguardFadingAway");
+            } else {
+                log("goingToFullShade && !keyguardFadingAway");
             }
         } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
             mView.setVisibility(View.VISIBLE);
@@ -105,6 +122,7 @@
                     .setDuration(320)
                     .setInterpolator(Interpolators.ALPHA_IN)
                     .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+            log("keyguardFadingAway transition w/ Y Aniamtion");
         } else if (statusBarState == KEYGUARD) {
             if (keyguardFadingAway) {
                 mKeyguardViewVisibilityAnimating = true;
@@ -125,9 +143,13 @@
                             true /* animate */);
                     animator.setDuration(duration)
                             .setStartDelay(delay);
+                    log("keyguardFadingAway transition w/ Y Aniamtion");
+                } else {
+                    log("keyguardFadingAway transition w/o Y Animation");
                 }
                 animator.start();
             } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
+                log("ScreenOff transition");
                 mKeyguardViewVisibilityAnimating = true;
 
                 // Ask the screen off animation controller to animate the keyguard visibility for us
@@ -136,6 +158,7 @@
                         mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
             } else if (!mIsUnoccludeTransitionFlagEnabled && mLastOccludedState && !isOccluded) {
                 // An activity was displayed over the lock screen, and has now gone away
+                log("Unoccluded transition");
                 mView.setVisibility(View.VISIBLE);
                 mView.setAlpha(0f);
 
@@ -146,12 +169,14 @@
                         .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
                         .start();
             } else {
+                log("Direct set Visibility to VISIBLE");
                 mView.setVisibility(View.VISIBLE);
                 if (!mIsUnoccludeTransitionFlagEnabled) {
                     mView.setAlpha(1f);
                 }
             }
         } else {
+            log("Direct set Visibility to GONE");
             mView.setVisibility(View.GONE);
             mView.setAlpha(1f);
         }
@@ -162,14 +187,18 @@
     private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
         mKeyguardViewVisibilityAnimating = false;
         mView.setVisibility(View.INVISIBLE);
+        log("Callback Set Visibility to INVISIBLE");
     };
 
     private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
         mKeyguardViewVisibilityAnimating = false;
         mView.setVisibility(View.GONE);
+        log("CallbackSet Visibility to GONE");
     };
 
     private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
         mKeyguardViewVisibilityAnimating = false;
+        mView.setVisibility(View.VISIBLE);
+        log("Callback Set Visibility to VISIBLE");
     };
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index b84fb08..b106fec 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -34,7 +34,7 @@
  * temporary logs or logs for smaller classes when creating whole new [LogBuffer] wrapper might be
  * an overkill.
  */
-class KeyguardLogger @Inject constructor(@KeyguardLog private val buffer: LogBuffer) :
+class KeyguardLogger @Inject constructor(@KeyguardLog val buffer: LogBuffer) :
     ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) {
 
     fun logException(ex: Exception, @CompileTimeConstant logMsg: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java
new file mode 100644
index 0000000..902bb18
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintInteractiveToAuthProvider.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+/** Provides the status of the interactive to auth feature. */
+public interface FingerprintInteractiveToAuthProvider {
+    /**
+     *
+     * @param userId the user Id.
+     * @return true if the InteractiveToAuthFeature is enabled, false if disabled.
+     */
+    boolean isEnabled(int userId);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2465286..e38c89e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -31,6 +31,7 @@
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
 import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
 import com.android.systemui.biometrics.dagger.BiometricsModule;
 import com.android.systemui.biometrics.dagger.UdfpsModule;
@@ -221,6 +222,9 @@
     @BindsOptionalOf
     abstract AlternateUdfpsTouchProvider optionalUdfpsTouchProvider();
 
+    @BindsOptionalOf
+    abstract FingerprintInteractiveToAuthProvider optionalFingerprintInteractiveToAuthProvider();
+
     @SysUISingleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7ae4d8a..25dee27 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -165,7 +165,7 @@
     // TODO(b/255618149): Tracking Bug
     @JvmField
     val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES =
-        unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = false)
+        unreleasedFlag(216, "customizable_lock_screen_quick_affordances", teamfood = true)
 
     /** Shows chipbar UI whenever the device is unlocked by ActiveUnlock (watch). */
     // TODO(b/256513609): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index b4934cf..bf5fbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -20,8 +20,7 @@
 import static com.android.systemui.screenshot.LogConfig.DEBUG_CALLBACK;
 import static com.android.systemui.screenshot.LogConfig.DEBUG_STORAGE;
 import static com.android.systemui.screenshot.LogConfig.logTag;
-import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION;
-import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS;
+import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType;
 
 import android.app.ActivityTaskManager;
 import android.app.Notification;
@@ -155,7 +154,8 @@
 
             CompletableFuture<List<Notification.Action>> smartActionsFuture =
                     mScreenshotSmartActions.getSmartActionsFuture(
-                            mScreenshotId, uri, image, mSmartActionsProvider, REGULAR_SMART_ACTIONS,
+                            mScreenshotId, uri, image, mSmartActionsProvider,
+                            ScreenshotSmartActionType.REGULAR_SMART_ACTIONS,
                             smartActionsEnabled, user);
             List<Notification.Action> smartActions = new ArrayList<>();
             if (smartActionsEnabled) {
@@ -166,7 +166,8 @@
                 smartActions.addAll(buildSmartActions(
                         mScreenshotSmartActions.getSmartActions(
                                 mScreenshotId, smartActionsFuture, timeoutMs,
-                                mSmartActionsProvider, REGULAR_SMART_ACTIONS),
+                                mSmartActionsProvider,
+                                ScreenshotSmartActionType.REGULAR_SMART_ACTIONS),
                         mContext));
             }
 
@@ -476,7 +477,7 @@
         CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
                 mScreenshotSmartActions.getSmartActionsFuture(
                         mScreenshotId, null, image, mSmartActionsProvider,
-                        QUICK_SHARE_ACTION,
+                        ScreenshotSmartActionType.QUICK_SHARE_ACTION,
                         true /* smartActionsEnabled */, user);
         int timeoutMs = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -485,7 +486,8 @@
         List<Notification.Action> quickShareActions =
                 mScreenshotSmartActions.getSmartActions(
                         mScreenshotId, quickShareActionsFuture, timeoutMs,
-                        mSmartActionsProvider, QUICK_SHARE_ACTION);
+                        mSmartActionsProvider,
+                        ScreenshotSmartActionType.QUICK_SHARE_ACTION);
         if (!quickShareActions.isEmpty()) {
             mQuickShareData.quickShareAction = quickShareActions.get(0);
             mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 5716a1d72..91ebf79 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -280,6 +280,7 @@
     private final TimeoutHandler mScreenshotHandler;
     private final ActionIntentExecutor mActionExecutor;
     private final UserManager mUserManager;
+    private final WorkProfileMessageController mWorkProfileMessageController;
 
     private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
         if (DEBUG_INPUT) {
@@ -326,7 +327,8 @@
             BroadcastSender broadcastSender,
             ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
             ActionIntentExecutor actionExecutor,
-            UserManager userManager
+            UserManager userManager,
+            WorkProfileMessageController workProfileMessageController
     ) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
@@ -358,6 +360,7 @@
         mFlags = flags;
         mActionExecutor = actionExecutor;
         mUserManager = userManager;
+        mWorkProfileMessageController = workProfileMessageController;
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
@@ -683,7 +686,6 @@
                         return true;
                     }
                 });
-
         if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
             mScreenshotView.badgeScreenshot(
                     mContext.getPackageManager().getUserBadgeForDensity(owner, 0));
@@ -784,9 +786,9 @@
             mLongScreenshotHolder.setLongScreenshot(longScreenshot);
             mLongScreenshotHolder.setTransitionDestinationCallback(
                     (transitionDestination, onTransitionEnd) -> {
-                            mScreenshotView.startLongScreenshotTransition(
-                                    transitionDestination, onTransitionEnd,
-                                    longScreenshot);
+                        mScreenshotView.startLongScreenshotTransition(
+                                transitionDestination, onTransitionEnd,
+                                longScreenshot);
                         // TODO: Do this via ActionIntentExecutor instead.
                         mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
                     }
@@ -1037,10 +1039,8 @@
 
     private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
         mScreenshotView.setChipIntents(imageData);
-        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)
-                && mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
-            // TODO: Read app from configuration
-            mScreenshotView.showWorkProfileMessage("Files");
+        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+            mWorkProfileMessageController.onScreenshotTaken(imageData.owner, mScreenshotView);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 736b892..be40813 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -33,6 +33,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.BroadcastOptions;
 import android.app.Notification;
@@ -102,7 +103,8 @@
  * Handles the visual elements and animations for the screenshot flow.
  */
 public class ScreenshotView extends FrameLayout implements
-        ViewTreeObserver.OnComputeInternalInsetsListener {
+        ViewTreeObserver.OnComputeInternalInsetsListener,
+        WorkProfileMessageController.WorkProfileMessageDisplay {
 
     interface ScreenshotViewCallback {
         void onUserInteraction();
@@ -358,13 +360,23 @@
      * been taken and which app can be used to view it.
      *
      * @param appName The name of the app to use to view screenshots
+     * @param appIcon Optional icon for the relevant files app
+     * @param onDismiss Runnable to be run when the user dismisses this message
      */
-    void showWorkProfileMessage(String appName) {
+    @Override
+    public void showWorkProfileMessage(CharSequence appName, @Nullable Drawable appIcon,
+            Runnable onDismiss) {
+        if (appIcon != null) {
+            // Replace the default icon if one is provided.
+            ImageView imageView = mMessageContainer.findViewById(R.id.screenshot_message_icon);
+            imageView.setImageDrawable(appIcon);
+        }
         mMessageContent.setText(
                 mContext.getString(R.string.screenshot_work_profile_notification, appName));
         mMessageContainer.setVisibility(VISIBLE);
         mMessageContainer.findViewById(R.id.message_dismiss_button).setOnClickListener((v) -> {
             mMessageContainer.setVisibility(View.GONE);
+            onDismiss.run();
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
new file mode 100644
index 0000000..5d7e56f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/WorkProfileMessageController.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * Handles all the non-UI portions of the work profile first run:
+ * - Track whether the user has already dismissed it.
+ * - Load the proper icon and app name.
+ */
+class WorkProfileMessageController
+@Inject
+constructor(
+    private val context: Context,
+    private val userManager: UserManager,
+    private val packageManager: PackageManager,
+) {
+
+    /**
+     * Determine if a message should be shown to the user, send message details to messageDisplay if
+     * appropriate.
+     */
+    fun onScreenshotTaken(userHandle: UserHandle, messageDisplay: WorkProfileMessageDisplay) {
+        if (userManager.isManagedProfile(userHandle.identifier) && !messageAlreadyDismissed()) {
+            var badgedIcon: Drawable? = null
+            var label: CharSequence? = null
+            val fileManager = fileManagerComponentName()
+            try {
+                val info =
+                    packageManager.getActivityInfo(
+                        fileManager,
+                        PackageManager.ComponentInfoFlags.of(0)
+                    )
+                val icon = packageManager.getActivityIcon(fileManager)
+                badgedIcon = packageManager.getUserBadgedIcon(icon, userHandle)
+                label = info.loadLabel(packageManager)
+            } catch (e: PackageManager.NameNotFoundException) {
+                Log.w(TAG, "Component $fileManager not found")
+            }
+
+            // If label wasn't loaded, use a default
+            val badgedLabel =
+                packageManager.getUserBadgedLabel(label ?: defaultFileAppName(), userHandle)
+
+            messageDisplay.showWorkProfileMessage(badgedLabel, badgedIcon) { onMessageDismissed() }
+        }
+    }
+
+    private fun messageAlreadyDismissed(): Boolean {
+        return sharedPreference().getBoolean(PREFERENCE_KEY, false)
+    }
+
+    private fun onMessageDismissed() {
+        val editor = sharedPreference().edit()
+        editor.putBoolean(PREFERENCE_KEY, true)
+        editor.apply()
+    }
+
+    private fun sharedPreference() =
+        context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
+
+    private fun fileManagerComponentName() =
+        ComponentName.unflattenFromString(
+            context.getString(R.string.config_sceenshotWorkProfileFilesApp)
+        )
+
+    private fun defaultFileAppName() = context.getString(R.string.screenshot_default_files_app_name)
+
+    /** UI that can show work profile messages (ScreenshotView in practice) */
+    interface WorkProfileMessageDisplay {
+        /**
+         * Show the given message and icon, calling onDismiss if the user explicitly dismisses the
+         * message.
+         */
+        fun showWorkProfileMessage(text: CharSequence, icon: Drawable?, onDismiss: Runnable)
+    }
+
+    companion object {
+        const val TAG = "WorkProfileMessageCtrl"
+        const val SHARED_PREFERENCES_NAME = "com.android.systemui.screenshot"
+        const val PREFERENCE_KEY = "work_profile_first_run"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index f63d652..c8ee647 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -160,7 +160,7 @@
         mStatusBarStateController = statusBarStateController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
-                screenOffAnimationController,  /* animateYPos= */ false);
+                screenOffAnimationController,  /* animateYPos= */ false, /* logBuffer= */ null);
         mUserSwitchDialogController = userSwitchDialogController;
         mUiEventLogger = uiEventLogger;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index c150654..e9f0dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -173,7 +173,7 @@
                 mUserSwitcherController, this);
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView,
                 keyguardStateController, dozeParameters,
-                screenOffAnimationController, /* animateYPos= */ false);
+                screenOffAnimationController, /* animateYPos= */ false, /* logBuffer= */ null);
         mBackground = new KeyguardUserSwitcherScrim(context);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 7d23399..0f9ae39 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1522,6 +1522,7 @@
                 .setDuration(mDialogHideAnimationDurationMs)
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
                 .withEndAction(() -> mHandler.postDelayed(() -> {
+                    mController.notifyVisible(false);
                     mDialog.dismiss();
                     tryToRemoveCaptionsTooltip();
                     mIsAnimatingDismiss = false;
@@ -1535,7 +1536,6 @@
         animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS,
                 mDialogHideAnimationDurationMs)).start();
         checkODICaptionsTooltip(true);
-        mController.notifyVisible(false);
         synchronized (mSafetyWarningLock) {
             if (mSafetyWarning != null) {
                 if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index be4bbdf..dfad15d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -24,6 +24,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 
+import com.android.keyguard.logging.KeyguardLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockAnimations;
@@ -65,6 +66,8 @@
     ScreenOffAnimationController mScreenOffAnimationController;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
+    @Mock
+    KeyguardLogger mKeyguardLogger;
 
     private KeyguardStatusViewController mController;
 
@@ -81,7 +84,8 @@
                 mConfigurationController,
                 mDozeParameters,
                 mFeatureFlags,
-                mScreenOffAnimationController);
+                mScreenOffAnimationController,
+                mKeyguardLogger);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 539dc55..ac22de9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -117,6 +117,7 @@
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.log.SessionTracker;
@@ -143,6 +144,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -234,6 +236,8 @@
     @Mock
     private GlobalSettings mGlobalSettings;
     private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
+    @Mock
+    private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider;
 
 
     private final int mCurrentUserId = 100;
@@ -1259,8 +1263,7 @@
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
 
         // WHEN require screen on to auth is disabled, and keyguard is not awake
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
-        mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+        when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false);
 
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
@@ -1280,8 +1283,7 @@
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
 
         // WHEN require screen on to auth is enabled, and keyguard is not awake
-        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
-        mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+        when(mInteractiveToAuthProvider.isEnabled(anyInt())).thenReturn(false);
 
         // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
         assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
@@ -2406,7 +2408,7 @@
                     mPowerManager, mTrustManager, mSubscriptionManager, mUserManager,
                     mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
                     mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
-                    mFaceWakeUpTriggersConfig);
+                    mFaceWakeUpTriggersConfig, Optional.of(mInteractiveToAuthProvider));
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
new file mode 100644
index 0000000..bd04b3c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/WorkProfileMessageControllerTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.util.FakeSharedPreferences;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class WorkProfileMessageControllerTest {
+    private static final String DEFAULT_LABEL = "default label";
+    private static final String BADGED_DEFAULT_LABEL = "badged default label";
+    private static final String APP_LABEL = "app label";
+    private static final String BADGED_APP_LABEL = "badged app label";
+    private static final UserHandle NON_WORK_USER = UserHandle.of(0);
+    private static final UserHandle WORK_USER = UserHandle.of(10);
+
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private Context mContext;
+    @Mock
+    private WorkProfileMessageController.WorkProfileMessageDisplay mMessageDisplay;
+    @Mock
+    private Drawable mActivityIcon;
+    @Mock
+    private Drawable mBadgedActivityIcon;
+    @Mock
+    private ActivityInfo mActivityInfo;
+    @Captor
+    private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+
+    private FakeSharedPreferences mSharedPreferences = new FakeSharedPreferences();
+
+    private WorkProfileMessageController mMessageController;
+
+    @Before
+    public void setup() throws PackageManager.NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+
+        when(mUserManager.isManagedProfile(eq(WORK_USER.getIdentifier()))).thenReturn(true);
+        when(mContext.getSharedPreferences(
+                eq(WorkProfileMessageController.SHARED_PREFERENCES_NAME),
+                eq(Context.MODE_PRIVATE))).thenReturn(mSharedPreferences);
+        when(mContext.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_LABEL);
+        when(mPackageManager.getUserBadgedLabel(eq(DEFAULT_LABEL), any()))
+                .thenReturn(BADGED_DEFAULT_LABEL);
+        when(mPackageManager.getUserBadgedLabel(eq(APP_LABEL), any()))
+                .thenReturn(BADGED_APP_LABEL);
+        when(mPackageManager.getActivityIcon(any(ComponentName.class)))
+                .thenReturn(mActivityIcon);
+        when(mPackageManager.getUserBadgedIcon(
+                any(), any())).thenReturn(mBadgedActivityIcon);
+        when(mPackageManager.getActivityInfo(any(),
+                any(PackageManager.ComponentInfoFlags.class))).thenReturn(mActivityInfo);
+        when(mActivityInfo.loadLabel(eq(mPackageManager))).thenReturn(APP_LABEL);
+
+        mSharedPreferences.edit().putBoolean(
+                WorkProfileMessageController.PREFERENCE_KEY, false).apply();
+
+        mMessageController = new WorkProfileMessageController(mContext, mUserManager,
+                mPackageManager);
+    }
+
+    @Test
+    public void testOnScreenshotTaken_notManaged() {
+        mMessageController.onScreenshotTaken(NON_WORK_USER, mMessageDisplay);
+
+        verify(mMessageDisplay, never())
+                .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+    }
+
+    @Test
+    public void testOnScreenshotTaken_alreadyDismissed() {
+        mSharedPreferences.edit().putBoolean(
+                WorkProfileMessageController.PREFERENCE_KEY, true).apply();
+
+        mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+
+        verify(mMessageDisplay, never())
+                .showWorkProfileMessage(any(), nullable(Drawable.class), any());
+    }
+
+    @Test
+    public void testOnScreenshotTaken_packageNotFound()
+            throws PackageManager.NameNotFoundException {
+        when(mPackageManager.getActivityInfo(any(),
+                any(PackageManager.ComponentInfoFlags.class))).thenThrow(
+                new PackageManager.NameNotFoundException());
+
+        mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+
+        verify(mMessageDisplay).showWorkProfileMessage(
+                eq(BADGED_DEFAULT_LABEL), eq(null), any());
+    }
+
+    @Test
+    public void testOnScreenshotTaken() {
+        mMessageController.onScreenshotTaken(WORK_USER, mMessageDisplay);
+
+        verify(mMessageDisplay).showWorkProfileMessage(
+                eq(BADGED_APP_LABEL), eq(mBadgedActivityIcon), mRunnableArgumentCaptor.capture());
+
+        // Dismiss hasn't been tapped, preference untouched.
+        assertFalse(
+                mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+
+        mRunnableArgumentCaptor.getValue().run();
+
+        // After dismiss has been tapped, the setting should be updated.
+        assertTrue(
+                mSharedPreferences.getBoolean(WorkProfileMessageController.PREFERENCE_KEY, false));
+    }
+}
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index c3c6975..d419095 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume;
 
+import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
 import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;
 
 import static junit.framework.Assert.assertEquals;
@@ -342,6 +343,15 @@
         assertEquals(mDialog.mVolumeRingerMuteIconDrawableId, R.drawable.ic_volume_ringer_mute);
     }
 
+    @Test
+    public void testDialogDismissAnimation_notifyVisibleIsNotCalledBeforeAnimation() {
+        mDialog.dismissH(DISMISS_REASON_UNKNOWN);
+        // notifyVisible(false) should not be called immediately but only after the dismiss
+        // animation has ended.
+        verify(mVolumeDialogController, times(0)).notifyVisible(false);
+        mDialog.getDialogView().animate().cancel();
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 99d6228..9fc0038 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -82,7 +82,9 @@
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
+    /*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4;
 
+    // ducking settings for a "normal duck" at -14dB
     private static final VolumeShaper.Configuration DUCK_VSHAPE =
             new VolumeShaper.Configuration.Builder()
                 .setId(VOLUME_SHAPER_SYSTEM_DUCK_ID)
@@ -96,6 +98,22 @@
                 .build();
     private static final VolumeShaper.Configuration DUCK_ID =
             new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_DUCK_ID);
+
+    // ducking settings for a "strong duck" at -35dB (attenuation factor of 0.017783)
+    private static final VolumeShaper.Configuration STRONG_DUCK_VSHAPE =
+            new VolumeShaper.Configuration.Builder()
+                .setId(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID)
+                .setCurve(new float[] { 0.f, 1.f } /* times */,
+                        new float[] { 1.f, 0.017783f } /* volumes */)
+                .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                .setDuration(MediaFocusControl.getFocusRampTimeMs(
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
+                        new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION)
+                                .build()))
+                    .build();
+    private static final VolumeShaper.Configuration STRONG_DUCK_ID =
+            new VolumeShaper.Configuration(VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID);
+
     private static final VolumeShaper.Operation PLAY_CREATE_IF_NEEDED =
             new VolumeShaper.Operation.Builder(VolumeShaper.Operation.PLAY)
                     .createIfNeeded()
@@ -784,11 +802,23 @@
             // add the players eligible for ducking to the list, and duck them
             // (if apcsToDuck is empty, this will at least mark this uid as ducked, so when
             //  players of the same uid start, they will be ducked by DuckingManager.checkDuck())
-            mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck);
+            mDuckingManager.duckUid(loser.getClientUid(), apcsToDuck, reqCausesStrongDuck(winner));
         }
         return true;
     }
 
+    private boolean reqCausesStrongDuck(FocusRequester requester) {
+        if (requester.getGainRequest() != AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
+            return false;
+        }
+        final int reqUsage = requester.getAudioAttributes().getUsage();
+        if ((reqUsage == AudioAttributes.USAGE_ASSISTANT)
+                || (reqUsage == AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
         if (DEBUG) { Log.v(TAG, "unduckPlayers: uids winner=" + winner.getClientUid()); }
@@ -1064,10 +1094,11 @@
     private static final class DuckingManager {
         private final HashMap<Integer, DuckedApp> mDuckers = new HashMap<Integer, DuckedApp>();
 
-        synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck) {
+        synchronized void duckUid(int uid, ArrayList<AudioPlaybackConfiguration> apcsToDuck,
+                boolean requestCausesStrongDuck) {
             if (DEBUG) {  Log.v(TAG, "DuckingManager: duckUid() uid:"+ uid); }
             if (!mDuckers.containsKey(uid)) {
-                mDuckers.put(uid, new DuckedApp(uid));
+                mDuckers.put(uid, new DuckedApp(uid, requestCausesStrongDuck));
             }
             final DuckedApp da = mDuckers.get(uid);
             for (AudioPlaybackConfiguration apc : apcsToDuck) {
@@ -1114,10 +1145,13 @@
 
         private static final class DuckedApp {
             private final int mUid;
+            /** determines whether ducking is done with DUCK_VSHAPE or STRONG_DUCK_VSHAPE */
+            private final boolean mUseStrongDuck;
             private final ArrayList<Integer> mDuckedPlayers = new ArrayList<Integer>();
 
-            DuckedApp(int uid) {
+            DuckedApp(int uid, boolean useStrongDuck) {
                 mUid = uid;
+                mUseStrongDuck = useStrongDuck;
             }
 
             void dump(PrintWriter pw) {
@@ -1138,9 +1172,10 @@
                     return;
                 }
                 try {
-                    sEventLogger.enqueue((new DuckEvent(apc, skipRamp)).printLog(TAG));
+                    sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck))
+                            .printLog(TAG));
                     apc.getPlayerProxy().applyVolumeShaper(
-                            DUCK_VSHAPE,
+                            mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE,
                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
                     mDuckedPlayers.add(piid);
                 } catch (Exception e) {
@@ -1156,7 +1191,7 @@
                             sEventLogger.enqueue((new EventLogger.StringEvent("unducking piid:"
                                     + piid)).printLog(TAG));
                             apc.getPlayerProxy().applyVolumeShaper(
-                                    DUCK_ID,
+                                    mUseStrongDuck ? STRONG_DUCK_ID : DUCK_ID,
                                     VolumeShaper.Operation.REVERSE);
                         } catch (Exception e) {
                             Log.e(TAG, "Error unducking player piid:" + piid + " uid:" + mUid, e);
@@ -1309,13 +1344,17 @@
     }
 
     static final class DuckEvent extends VolumeShaperEvent {
+        final boolean mUseStrongDuck;
+
         @Override
         String getVSAction() {
-            return "ducking";
+            return mUseStrongDuck ? "ducking (strong)" : "ducking";
         }
 
-        DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+        DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)
+        {
             super(apc, skipRamp);
+            mUseStrongDuck = useStrongDuck;
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 9669950..a90679e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,15 +16,11 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
-import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskStackListener;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
 import android.hardware.biometrics.BiometricManager.Authenticators;
@@ -94,7 +90,6 @@
     private long mSideFpsLastAcquireStartTime;
     private Runnable mAuthSuccessRunnable;
     private final Clock mClock;
-    private boolean mDidFinishSfps;
 
     FingerprintAuthenticationClient(
             @NonNull Context context,
@@ -204,9 +199,8 @@
 
     @Override
     protected void handleLifecycleAfterAuth(boolean authenticated) {
-        if (authenticated && !mDidFinishSfps) {
+        if (authenticated) {
             mCallback.onClientFinished(this, true /* success */);
-            mDidFinishSfps = true;
         }
     }
 
@@ -216,13 +210,11 @@
         return false;
     }
 
-    public void handleAuthenticate(
+    @Override
+    public void onAuthenticated(
             BiometricAuthenticator.Identifier identifier,
             boolean authenticated,
             ArrayList<Byte> token) {
-        if (authenticated && mSensorProps.isAnySidefpsType()) {
-            Slog.i(TAG, "(sideFPS): No power press detected, sending auth");
-        }
         super.onAuthenticated(identifier, authenticated, token);
         if (authenticated) {
             mState = STATE_STOPPED;
@@ -233,74 +225,13 @@
     }
 
     @Override
-    public void onAuthenticated(
-            BiometricAuthenticator.Identifier identifier,
-            boolean authenticated,
-            ArrayList<Byte> token) {
-
-        mHandler.post(
-                () -> {
-                    long delay = 0;
-                    if (authenticated && mSensorProps.isAnySidefpsType()) {
-                        delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
-
-                        if (mSideFpsLastAcquireStartTime != -1) {
-                            delay = Math.max(0,
-                                    delay - (mClock.millis() - mSideFpsLastAcquireStartTime));
-                        }
-
-                        Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps "
-                                + "waiting for power until: " + delay + "ms");
-                    }
-
-                    if (mHandler.hasMessages(MESSAGE_FINGER_UP)) {
-                        Slog.i(TAG, "Finger up detected, sending auth");
-                        delay = 0;
-                    }
-
-                    mAuthSuccessRunnable =
-                            () -> handleAuthenticate(identifier, authenticated, token);
-                    mHandler.postDelayed(
-                            mAuthSuccessRunnable,
-                            MESSAGE_AUTH_SUCCESS,
-                            delay);
-                });
-    }
-
-    @Override
     public void onAcquired(@FingerprintAcquired int acquiredInfo, int vendorCode) {
         // For UDFPS, notify SysUI with acquiredInfo, so that the illumination can be turned off
         // for most ACQUIRED messages. See BiometricFingerprintConstants#FingerprintAcquired
         mSensorOverlays.ifUdfps(controller -> controller.onAcquired(getSensorId(), acquiredInfo));
         super.onAcquired(acquiredInfo, vendorCode);
-        if (mSensorProps.isAnySidefpsType()) {
-            if (acquiredInfo == FINGERPRINT_ACQUIRED_START) {
-                mSideFpsLastAcquireStartTime = mClock.millis();
-            }
-            final boolean shouldLookForVendor =
-                    mSkipWaitForPowerAcquireMessage == FINGERPRINT_ACQUIRED_VENDOR;
-            final boolean acquireMessageMatch = acquiredInfo == mSkipWaitForPowerAcquireMessage;
-            final boolean vendorMessageMatch = vendorCode == mSkipWaitForPowerVendorAcquireMessage;
-            final boolean ignorePowerPress =
-                    acquireMessageMatch && (!shouldLookForVendor || vendorMessageMatch);
-
-            if (ignorePowerPress) {
-                Slog.d(TAG, "(sideFPS) onFingerUp");
-                mHandler.post(() -> {
-                    if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
-                        Slog.d(TAG, "(sideFPS) skipping wait for power");
-                        mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
-                        mHandler.post(mAuthSuccessRunnable);
-                    } else {
-                        mHandler.postDelayed(() -> {
-                        }, MESSAGE_FINGER_UP, mFingerUpIgnoresPower);
-                    }
-                });
-            }
-        }
         PerformanceTracker pt = PerformanceTracker.getInstanceForSensorId(getSensorId());
         pt.incrementAcquireForUser(getTargetUserId(), isCryptoOperation());
-
     }
 
     @Override
@@ -495,22 +426,5 @@
     }
 
     @Override
-    public void onPowerPressed() {
-        if (mSensorProps.isAnySidefpsType()) {
-            Slog.i(TAG, "(sideFPS): onPowerPressed");
-            mHandler.post(() -> {
-                if (mDidFinishSfps) {
-                    return;
-                }
-                Slog.i(TAG, "(sideFPS): finishing auth");
-                // Ignore auths after a power has been detected
-                mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
-                // Do not call onError() as that will send an additional callback to coex.
-                mDidFinishSfps = true;
-                onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
-                stopHalOperation();
-                mSensorOverlays.hide(getSensorId());
-            });
-        }
-    }
+    public void onPowerPressed() { }
 }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1b8f6e3..cdaa3d0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -814,9 +814,9 @@
                 mDisplayDeviceConfig = config;
                 loadFromDisplayDeviceConfig(token, info);
 
-                // Since the underlying display-device changed, we really don't know the
-                // last command that was sent to change it's state. Lets assume it is off and we
-                // trigger a change immediately.
+                /// Since the underlying display-device changed, we really don't know the
+                // last command that was sent to change it's state. Lets assume it is unknown so
+                // that we trigger a change immediately.
                 mPowerState.resetScreenState();
             }
             if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 7d1396d..2c257a1 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -340,20 +340,12 @@
     }
 
     /**
-     * Resets the screen state to {@link Display#STATE_OFF}. Even though we do not know the last
-     * state that was sent to the underlying display-device, we assume it is off.
-     *
-     * We do not set the screen state to {@link Display#STATE_UNKNOWN} to avoid getting in the state
-     * where PhotonicModulator holds onto the lock. This happens because we currently try to keep
-     * the mScreenState and mPendingState in sync, however if the screenState is set to
-     * {@link Display#STATE_UNKNOWN} here, mPendingState will get progressed to this, which will
-     * force the PhotonicModulator thread to wait onto the lock to take it out of that state.
-     * b/262294651 for more info.
+     * Resets the screen state to unknown. Useful when the underlying display-device changes for the
+     * LogicalDisplay and we do not know the last state that was sent to it.
      */
     void resetScreenState() {
-        mScreenState = Display.STATE_OFF;
+        mScreenState = Display.STATE_UNKNOWN;
         mScreenReady = false;
-        scheduleScreenUpdate();
     }
 
     private void scheduleScreenUpdate() {
@@ -514,6 +506,8 @@
                     boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState);
                     boolean changed = stateChanged || backlightChanged;
                     if (!valid || !changed) {
+                        mStateChangeInProgress = false;
+                        mBacklightChangeInProgress = false;
                         try {
                             mLock.wait();
                         } catch (InterruptedException ex) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 11a4294..6cfe921 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -21,6 +21,7 @@
 import android.app.IGrammaticalInflectionManager;
 import android.content.Context;
 import android.os.IBinder;
+import android.os.SystemProperties;
 
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -34,6 +35,8 @@
 public class GrammaticalInflectionService extends SystemService {
 
     private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    private static final String GRAMMATICAL_INFLECTION_ENABLED =
+            "i18n.grammatical_Inflection.enabled";
 
     /**
      * Initializes the system service.
@@ -67,6 +70,10 @@
 
     private void setRequestedApplicationGrammaticalGender(
             String appPackageName, int userId, int gender) {
+        if (!SystemProperties.getBoolean(GRAMMATICAL_INFLECTION_ENABLED, true)) {
+            return;
+        }
+
         final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
                 mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
                         userId);
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 5353092..6b5af88 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -619,6 +619,8 @@
     })
     @interface HpdSignalType {}
 
+    static final String DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE = "soundbar_mode";
+
     private Constants() {
         /* cannot be instantiated */
     }
diff --git a/services/core/java/com/android/server/hdmi/DeviceConfigWrapper.java b/services/core/java/com/android/server/hdmi/DeviceConfigWrapper.java
new file mode 100644
index 0000000..f391897
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DeviceConfigWrapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.provider.DeviceConfig;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Abstraction around {@link DeviceConfig} to allow faking DeviceConfig in tests.
+ */
+public class DeviceConfigWrapper {
+    private static final String TAG = "DeviceConfigWrapper";
+
+    boolean getBoolean(String name, boolean defaultValue) {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_HDMI_CONTROL, name, defaultValue);
+    }
+
+    void addOnPropertiesChangedListener(Executor mainExecutor,
+            DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
+        DeviceConfig.addOnPropertiesChangedListener(
+                DeviceConfig.NAMESPACE_HDMI_CONTROL, mainExecutor, onPropertiesChangedListener);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index f66f8ea..f6566d8 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -86,6 +86,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.DeviceConfig;
 import android.provider.Settings.Global;
 import android.sysprop.HdmiProperties;
 import android.text.TextUtils;
@@ -450,6 +451,9 @@
     private boolean mWakeUpMessageReceived = false;
 
     @ServiceThreadOnly
+    private boolean mSoundbarModeFeatureFlagEnabled = false;
+
+    @ServiceThreadOnly
     private int mActivePortId = Constants.INVALID_PORT_ID;
 
     // Set to true while the input change by MHL is allowed.
@@ -471,6 +475,9 @@
     private TvInputManager mTvInputManager;
 
     @Nullable
+    private DeviceConfigWrapper mDeviceConfig;
+
+    @Nullable
     private PowerManagerWrapper mPowerManager;
 
     @Nullable
@@ -525,6 +532,7 @@
         mCecLocalDevices = deviceTypes;
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
+        mDeviceConfig = new DeviceConfigWrapper();
         mAudioDeviceVolumeManager = audioDeviceVolumeManager;
     }
 
@@ -533,6 +541,7 @@
         mCecLocalDevices = readDeviceTypes();
         mSettingsObserver = new SettingsObserver(mHandler);
         mHdmiCecConfig = new HdmiCecConfig(context);
+        mDeviceConfig = new DeviceConfigWrapper();
     }
 
     @VisibleForTesting
@@ -763,14 +772,6 @@
                         }
                     }
                 }, mServiceThreadExecutor);
-        mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
-                new HdmiCecConfig.SettingChangeListener() {
-                    @Override
-                    public void onChange(String setting) {
-                        setSoundbarMode(mHdmiCecConfig.getIntValue(
-                                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE));
-                    }
-                }, mServiceThreadExecutor);
         mHdmiCecConfig.registerChangeListener(
                 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL,
                 new HdmiCecConfig.SettingChangeListener() {
@@ -819,9 +820,38 @@
                                 HdmiControlManager.SETTING_NAME_EARC_ENABLED);
                         setEarcEnabled(enabled);
                     }
+                },
+                mServiceThreadExecutor);
+
+        mSoundbarModeFeatureFlagEnabled = mDeviceConfig.getBoolean(
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, false);
+
+        mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(),
+                new DeviceConfig.OnPropertiesChangedListener() {
+                    @Override
+                    public void onPropertiesChanged(DeviceConfig.Properties properties) {
+                        mSoundbarModeFeatureFlagEnabled = properties.getBoolean(
+                                Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE,
+                                false);
+                        boolean soundbarModeSetting = mHdmiCecConfig.getIntValue(
+                                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+                                == SOUNDBAR_MODE_ENABLED;
+                        setSoundbarMode(soundbarModeSetting && mSoundbarModeFeatureFlagEnabled
+                                ? SOUNDBAR_MODE_ENABLED : SOUNDBAR_MODE_DISABLED);
+                    }
+                });
+        mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        boolean soundbarModeSetting = mHdmiCecConfig.getIntValue(
+                                HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+                                == SOUNDBAR_MODE_ENABLED;
+                        setSoundbarMode(soundbarModeSetting && mSoundbarModeFeatureFlagEnabled
+                                ? SOUNDBAR_MODE_ENABLED : SOUNDBAR_MODE_DISABLED);
+                    }
                 }, mServiceThreadExecutor);
     }
-
     /** Returns true if the device screen is off */
     boolean isScreenOff() {
         return mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_OFF;
@@ -920,6 +950,11 @@
     }
 
     @VisibleForTesting
+    void setDeviceConfig(DeviceConfigWrapper deviceConfig) {
+        mDeviceConfig = deviceConfig;
+    }
+
+    @VisibleForTesting
     void setPowerManager(PowerManagerWrapper powerManager) {
         mPowerManager = powerManager;
     }
@@ -929,6 +964,10 @@
         mPowerManagerInternal = powerManagerInternal;
     }
 
+    DeviceConfigWrapper getDeviceConfig() {
+        return mDeviceConfig;
+    }
+
     PowerManagerWrapper getPowerManager() {
         return mPowerManager;
     }
@@ -1151,7 +1190,8 @@
         if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
                 == SOUNDBAR_MODE_ENABLED
                 && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
-                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
+                && mSoundbarModeFeatureFlagEnabled) {
             allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
         }
         return allLocalDeviceTypes;
diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java
index 950e094..7ba7769 100644
--- a/services/core/java/com/android/server/input/KeyRemapper.java
+++ b/services/core/java/com/android/server/input/KeyRemapper.java
@@ -21,6 +21,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.ArrayMap;
+import android.util.FeatureFlagUtils;
 import android.view.InputDevice;
 
 import com.android.internal.annotations.GuardedBy;
@@ -65,16 +67,25 @@
     }
 
     public void remapKey(int fromKey, int toKey) {
+        if (!supportRemapping()) {
+            return;
+        }
         Message msg = Message.obtain(mHandler, MSG_REMAP_KEY, fromKey, toKey);
         mHandler.sendMessage(msg);
     }
 
     public void clearAllKeyRemappings() {
+        if (!supportRemapping()) {
+            return;
+        }
         Message msg = Message.obtain(mHandler, MSG_CLEAR_ALL_REMAPPING);
         mHandler.sendMessage(msg);
     }
 
     public Map<Integer, Integer> getKeyRemapping() {
+        if (!supportRemapping()) {
+            return new ArrayMap<>();
+        }
         synchronized (mDataStore) {
             return mDataStore.getKeyRemapping();
         }
@@ -124,6 +135,9 @@
 
     @Override
     public void onInputDeviceAdded(int deviceId) {
+        if (!supportRemapping()) {
+            return;
+        }
         InputManager inputManager = Objects.requireNonNull(
                 mContext.getSystemService(InputManager.class));
         InputDevice inputDevice = inputManager.getInputDevice(deviceId);
@@ -158,4 +172,9 @@
         }
         return false;
     }
+
+    private boolean supportRemapping() {
+        return FeatureFlagUtils.isEnabled(mContext,
+                FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 4d525da..9b42cfc 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -36,6 +36,7 @@
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -103,12 +104,12 @@
 
     /**
      * Number of boots until we consider the escrow data to be stale for the purposes of metrics.
-     * <p>
-     * If the delta between the current boot number and the boot number stored when the mechanism
+     *
+     * <p>If the delta between the current boot number and the boot number stored when the mechanism
      * was armed is under this number and the escrow mechanism fails, we report it as a failure of
      * the mechanism.
-     * <p>
-     * If the delta over this number and escrow fails, we will not report the metric as failed
+     *
+     * <p>If the delta over this number and escrow fails, we will not report the metric as failed
      * since there most likely was some other issue if the device rebooted several times before
      * getting to the escrow restore code.
      */
@@ -120,8 +121,11 @@
      */
     private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_COUNT = 3;
     private static final int DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS = 30;
+
     // 3 minutes. It's enough for the default 3 retries with 30 seconds interval
-    private static final int DEFAULT_WAKE_LOCK_TIMEOUT_MILLIS = 180_000;
+    private static final int DEFAULT_LOAD_ESCROW_BASE_TIMEOUT_MILLIS = 180_000;
+    // 5 seconds. An extension of the overall RoR timeout to account for overhead.
+    private static final int DEFAULT_LOAD_ESCROW_TIMEOUT_EXTENSION_MILLIS = 5000;
 
     @IntDef(prefix = {"ERROR_"}, value = {
             ERROR_NONE,
@@ -133,6 +137,7 @@
             ERROR_PROVIDER_MISMATCH,
             ERROR_KEYSTORE_FAILURE,
             ERROR_NO_NETWORK,
+            ERROR_TIMEOUT_EXHAUSTED,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface RebootEscrowErrorCode {
@@ -147,6 +152,7 @@
     static final int ERROR_PROVIDER_MISMATCH = 6;
     static final int ERROR_KEYSTORE_FAILURE = 7;
     static final int ERROR_NO_NETWORK = 8;
+    static final int ERROR_TIMEOUT_EXHAUSTED = 9;
 
     private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;
 
@@ -168,6 +174,15 @@
     /** Notified when mRebootEscrowReady changes. */
     private RebootEscrowListener mRebootEscrowListener;
 
+    /** Set when unlocking reboot escrow times out. */
+    private boolean mRebootEscrowTimedOut = false;
+
+    /**
+     * Set when {@link #loadRebootEscrowDataWithRetry} is called to ensure the function is only
+     * called once.
+     */
+    private boolean mLoadEscrowDataWithRetry = false;
+
     /**
      * Hold this lock when checking or generating the reboot escrow key.
      */
@@ -192,6 +207,7 @@
 
     PowerManager.WakeLock mWakeLock;
 
+    private ConnectivityManager.NetworkCallback mNetworkCallback;
 
     interface Callbacks {
         boolean isUserSecure(int userId);
@@ -246,6 +262,11 @@
                     "server_based_ror_enabled", false);
         }
 
+        public boolean waitForInternet() {
+            return DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_OTA, "wait_for_internet_ror", false);
+        }
+
         public boolean isNetworkConnected() {
             final ConnectivityManager connectivityManager =
                     mContext.getSystemService(ConnectivityManager.class);
@@ -263,6 +284,38 @@
                             NetworkCapabilities.NET_CAPABILITY_VALIDATED);
         }
 
+        /**
+         * Request network with internet connectivity with timeout.
+         *
+         * @param networkCallback callback to be executed if connectivity manager exists.
+         * @return true if success
+         */
+        public boolean requestNetworkWithInternet(
+                ConnectivityManager.NetworkCallback networkCallback) {
+            final ConnectivityManager connectivityManager =
+                    mContext.getSystemService(ConnectivityManager.class);
+            if (connectivityManager == null) {
+                return false;
+            }
+            NetworkRequest request =
+                    new NetworkRequest.Builder()
+                            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                            .build();
+
+            connectivityManager.requestNetwork(
+                    request, networkCallback, getLoadEscrowTimeoutMillis());
+            return true;
+        }
+
+        public void stopRequestingNetwork(ConnectivityManager.NetworkCallback networkCallback) {
+            final ConnectivityManager connectivityManager =
+                    mContext.getSystemService(ConnectivityManager.class);
+            if (connectivityManager == null) {
+                return;
+            }
+            connectivityManager.unregisterNetworkCallback(networkCallback);
+        }
+
         public Context getContext() {
             return mContext;
         }
@@ -318,6 +371,16 @@
                     DEFAULT_LOAD_ESCROW_DATA_RETRY_INTERVAL_SECONDS);
         }
 
+        @VisibleForTesting
+        public int getLoadEscrowTimeoutMillis() {
+            return DEFAULT_LOAD_ESCROW_BASE_TIMEOUT_MILLIS;
+        }
+
+        @VisibleForTesting
+        public int getWakeLockTimeoutMillis() {
+            return getLoadEscrowTimeoutMillis() + DEFAULT_LOAD_ESCROW_TIMEOUT_EXTENSION_MILLIS;
+        }
+
         public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
                 int escrowDurationInSeconds, int vbmetaDigestStatus,
                 int durationSinceBootCompleteInSeconds) {
@@ -351,13 +414,37 @@
         mKeyStoreManager = injector.getKeyStoreManager();
     }
 
-    private void onGetRebootEscrowKeyFailed(List<UserInfo> users, int attemptCount) {
+    /** Wrapper function to set error code serialized through handler, */
+    private void setLoadEscrowDataErrorCode(@RebootEscrowErrorCode int value, Handler handler) {
+        if (mInjector.waitForInternet()) {
+            mInjector.post(
+                    handler,
+                    () -> {
+                        mLoadEscrowDataErrorCode = value;
+                    });
+        } else {
+            mLoadEscrowDataErrorCode = value;
+        }
+    }
+
+    /** Wrapper function to compare and set error code serialized through handler. */
+    private void compareAndSetLoadEscrowDataErrorCode(
+            @RebootEscrowErrorCode int expectedValue,
+            @RebootEscrowErrorCode int newValue,
+            Handler handler) {
+        if (expectedValue == mLoadEscrowDataErrorCode) {
+            setLoadEscrowDataErrorCode(newValue, handler);
+        }
+    }
+
+    private void onGetRebootEscrowKeyFailed(
+            List<UserInfo> users, int attemptCount, Handler retryHandler) {
         Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
         for (UserInfo user : users) {
             mStorage.removeRebootEscrow(user.id);
         }
 
-        onEscrowRestoreComplete(false, attemptCount);
+        onEscrowRestoreComplete(false, attemptCount, retryHandler);
     }
 
     void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
@@ -380,39 +467,130 @@
         mWakeLock = mInjector.getWakeLock();
         if (mWakeLock != null) {
             mWakeLock.setReferenceCounted(false);
-            mWakeLock.acquire(DEFAULT_WAKE_LOCK_TIMEOUT_MILLIS);
+            mWakeLock.acquire(mInjector.getWakeLockTimeoutMillis());
+        }
+
+        if (mInjector.waitForInternet()) {
+            // Timeout to stop retrying same as the wake lock timeout.
+            mInjector.postDelayed(
+                    retryHandler,
+                    () -> {
+                        mRebootEscrowTimedOut = true;
+                    },
+                    mInjector.getLoadEscrowTimeoutMillis());
+
+            mInjector.post(
+                    retryHandler,
+                    () -> loadRebootEscrowDataOnInternet(retryHandler, users, rebootEscrowUsers));
+            return;
         }
 
         mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry(
                 retryHandler, 0, users, rebootEscrowUsers));
     }
 
-    void scheduleLoadRebootEscrowDataOrFail(Handler retryHandler, int attemptNumber,
-            List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
+    void scheduleLoadRebootEscrowDataOrFail(
+            Handler retryHandler,
+            int attemptNumber,
+            List<UserInfo> users,
+            List<UserInfo> rebootEscrowUsers) {
         Objects.requireNonNull(retryHandler);
 
         final int retryLimit = mInjector.getLoadEscrowDataRetryLimit();
         final int retryIntervalInSeconds = mInjector.getLoadEscrowDataRetryIntervalSeconds();
 
-        if (attemptNumber < retryLimit) {
+        if (attemptNumber < retryLimit && !mRebootEscrowTimedOut) {
             Slog.i(TAG, "Scheduling loadRebootEscrowData retry number: " + attemptNumber);
             mInjector.postDelayed(retryHandler, () -> loadRebootEscrowDataWithRetry(
-                    retryHandler, attemptNumber, users, rebootEscrowUsers),
+                            retryHandler, attemptNumber, users, rebootEscrowUsers),
                     retryIntervalInSeconds * 1000);
             return;
         }
 
+        if (mInjector.waitForInternet()) {
+            if (mRebootEscrowTimedOut) {
+                Slog.w(TAG, "Failed to load reboot escrow data within timeout");
+                compareAndSetLoadEscrowDataErrorCode(
+                        ERROR_NONE, ERROR_TIMEOUT_EXHAUSTED, retryHandler);
+            } else {
+                Slog.w(
+                        TAG,
+                        "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+                compareAndSetLoadEscrowDataErrorCode(
+                        ERROR_NONE, ERROR_RETRY_COUNT_EXHAUSTED, retryHandler);
+            }
+            onGetRebootEscrowKeyFailed(users, attemptNumber, retryHandler);
+            return;
+        }
+
         Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
         if (mInjector.serverBasedResumeOnReboot() && !mInjector.isNetworkConnected()) {
             mLoadEscrowDataErrorCode = ERROR_NO_NETWORK;
         } else {
             mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED;
         }
-        onGetRebootEscrowKeyFailed(users, attemptNumber);
+        onGetRebootEscrowKeyFailed(users, attemptNumber, retryHandler);
     }
 
-    void loadRebootEscrowDataWithRetry(Handler retryHandler, int attemptNumber,
-            List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
+    void loadRebootEscrowDataOnInternet(
+            Handler retryHandler, List<UserInfo> users, List<UserInfo> rebootEscrowUsers) {
+
+        // HAL-Based RoR does not require network connectivity.
+        if (!mInjector.serverBasedResumeOnReboot()) {
+            loadRebootEscrowDataWithRetry(
+                    retryHandler, /* attemptNumber = */ 0, users, rebootEscrowUsers);
+            return;
+        }
+
+        mNetworkCallback =
+                new ConnectivityManager.NetworkCallback() {
+                    @Override
+                    public void onAvailable(Network network) {
+                        compareAndSetLoadEscrowDataErrorCode(
+                                ERROR_NO_NETWORK, ERROR_NONE, retryHandler);
+
+                        if (!mLoadEscrowDataWithRetry) {
+                            mLoadEscrowDataWithRetry = true;
+                            // Only kickoff retry mechanism on first onAvailable call.
+                            loadRebootEscrowDataWithRetry(
+                                    retryHandler,
+                                    /* attemptNumber = */ 0,
+                                    users,
+                                    rebootEscrowUsers);
+                        }
+                    }
+
+                    @Override
+                    public void onUnavailable() {
+                        Slog.w(TAG, "Failed to connect to network within timeout");
+                        compareAndSetLoadEscrowDataErrorCode(
+                                ERROR_NONE, ERROR_NO_NETWORK, retryHandler);
+                        onGetRebootEscrowKeyFailed(users, /* attemptCount= */ 0, retryHandler);
+                    }
+
+                    @Override
+                    public void onLost(Network lostNetwork) {
+                        // TODO(b/231660348): If network is lost, wait for network to become
+                        // available again.
+                        Slog.w(TAG, "Network lost, still attempting to load escrow key.");
+                        compareAndSetLoadEscrowDataErrorCode(
+                                ERROR_NONE, ERROR_NO_NETWORK, retryHandler);
+                    }
+                };
+
+        // Fallback to retrying without waiting for internet on failure.
+        boolean success = mInjector.requestNetworkWithInternet(mNetworkCallback);
+        if (!success) {
+            loadRebootEscrowDataWithRetry(
+                    retryHandler, /* attemptNumber = */ 0, users, rebootEscrowUsers);
+        }
+    }
+
+    void loadRebootEscrowDataWithRetry(
+            Handler retryHandler,
+            int attemptNumber,
+            List<UserInfo> users,
+            List<UserInfo> rebootEscrowUsers) {
         // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
         // generated before reboot. Note that we will clear the escrow key even if the keystore key
         // is null.
@@ -423,7 +601,7 @@
 
         RebootEscrowKey escrowKey;
         try {
-            escrowKey = getAndClearRebootEscrowKey(kk);
+            escrowKey = getAndClearRebootEscrowKey(kk, retryHandler);
         } catch (IOException e) {
             Slog.i(TAG, "Failed to load escrow key, scheduling retry.", e);
             scheduleLoadRebootEscrowDataOrFail(retryHandler, attemptNumber + 1, users,
@@ -438,12 +616,12 @@
                         ? RebootEscrowProviderInterface.TYPE_SERVER_BASED
                         : RebootEscrowProviderInterface.TYPE_HAL;
                 if (providerType != mStorage.getInt(REBOOT_ESCROW_KEY_PROVIDER, -1, USER_SYSTEM)) {
-                    mLoadEscrowDataErrorCode = ERROR_PROVIDER_MISMATCH;
+                    setLoadEscrowDataErrorCode(ERROR_PROVIDER_MISMATCH, retryHandler);
                 } else {
-                    mLoadEscrowDataErrorCode = ERROR_LOAD_ESCROW_KEY;
+                    setLoadEscrowDataErrorCode(ERROR_LOAD_ESCROW_KEY, retryHandler);
                 }
             }
-            onGetRebootEscrowKeyFailed(users, attemptNumber + 1);
+            onGetRebootEscrowKeyFailed(users, attemptNumber + 1, retryHandler);
             return;
         }
 
@@ -454,10 +632,10 @@
             allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey, kk);
         }
 
-        if (!allUsersUnlocked && mLoadEscrowDataErrorCode == ERROR_NONE) {
-            mLoadEscrowDataErrorCode = ERROR_UNLOCK_ALL_USERS;
+        if (!allUsersUnlocked) {
+            compareAndSetLoadEscrowDataErrorCode(ERROR_NONE, ERROR_UNLOCK_ALL_USERS, retryHandler);
         }
-        onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1);
+        onEscrowRestoreComplete(allUsersUnlocked, attemptNumber + 1, retryHandler);
     }
 
     private void clearMetricsStorage() {
@@ -497,7 +675,8 @@
                 .REBOOT_ESCROW_RECOVERY_REPORTED__VBMETA_DIGEST_STATUS__MISMATCH;
     }
 
-    private void reportMetricOnRestoreComplete(boolean success, int attemptCount) {
+    private void reportMetricOnRestoreComplete(
+            boolean success, int attemptCount, Handler retryHandler) {
         int serviceType = mInjector.serverBasedResumeOnReboot()
                 ? FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__SERVER_BASED
                 : FrameworkStatsLog.REBOOT_ESCROW_RECOVERY_REPORTED__TYPE__HAL;
@@ -511,52 +690,69 @@
         }
 
         int vbmetaDigestStatus = getVbmetaDigestStatusOnRestoreComplete();
-        if (!success && mLoadEscrowDataErrorCode == ERROR_NONE) {
-            mLoadEscrowDataErrorCode = ERROR_UNKNOWN;
+        if (!success) {
+            compareAndSetLoadEscrowDataErrorCode(ERROR_NONE, ERROR_UNKNOWN, retryHandler);
         }
 
-        Slog.i(TAG, "Reporting RoR recovery metrics, success: " + success + ", service type: "
-                + serviceType + ", error code: " + mLoadEscrowDataErrorCode);
+        Slog.i(
+                TAG,
+                "Reporting RoR recovery metrics, success: "
+                        + success
+                        + ", service type: "
+                        + serviceType
+                        + ", error code: "
+                        + mLoadEscrowDataErrorCode);
         // TODO(179105110) report the duration since boot complete.
-        mInjector.reportMetric(success, mLoadEscrowDataErrorCode, serviceType, attemptCount,
-                escrowDurationInSeconds, vbmetaDigestStatus, -1);
+        mInjector.reportMetric(
+                success,
+                mLoadEscrowDataErrorCode,
+                serviceType,
+                attemptCount,
+                escrowDurationInSeconds,
+                vbmetaDigestStatus,
+                -1);
 
-        mLoadEscrowDataErrorCode = ERROR_NONE;
+        setLoadEscrowDataErrorCode(ERROR_NONE, retryHandler);
     }
 
-    private void onEscrowRestoreComplete(boolean success, int attemptCount) {
+    private void onEscrowRestoreComplete(boolean success, int attemptCount, Handler retryHandler) {
         int previousBootCount = mStorage.getInt(REBOOT_ESCROW_ARMED_KEY, -1, USER_SYSTEM);
 
         int bootCountDelta = mInjector.getBootCount() - previousBootCount;
         if (success || (previousBootCount != -1 && bootCountDelta <= BOOT_COUNT_TOLERANCE)) {
-            reportMetricOnRestoreComplete(success, attemptCount);
+            reportMetricOnRestoreComplete(success, attemptCount, retryHandler);
         }
-
         // Clear the old key in keystore. A new key will be generated by new RoR requests.
         mKeyStoreManager.clearKeyStoreEncryptionKey();
         // Clear the saved reboot escrow provider
         mInjector.clearRebootEscrowProvider();
         clearMetricsStorage();
 
+        if (mNetworkCallback != null) {
+            mInjector.stopRequestingNetwork(mNetworkCallback);
+        }
+
         if (mWakeLock != null) {
             mWakeLock.release();
         }
     }
 
-    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) throws IOException {
+    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk, Handler retryHandler)
+            throws IOException {
         RebootEscrowProviderInterface rebootEscrowProvider =
                 mInjector.createRebootEscrowProviderIfNeeded();
         if (rebootEscrowProvider == null) {
-            Slog.w(TAG,
+            Slog.w(
+                    TAG,
                     "Had reboot escrow data for users, but RebootEscrowProvider is unavailable");
-            mLoadEscrowDataErrorCode = ERROR_NO_PROVIDER;
+            setLoadEscrowDataErrorCode(ERROR_NO_PROVIDER, retryHandler);
             return null;
         }
 
         // Server based RoR always need the decryption key from keystore.
         if (rebootEscrowProvider.getType() == RebootEscrowProviderInterface.TYPE_SERVER_BASED
                 && kk == null) {
-            mLoadEscrowDataErrorCode = ERROR_KEYSTORE_FAILURE;
+            setLoadEscrowDataErrorCode(ERROR_KEYSTORE_FAILURE, retryHandler);
             return null;
         }
 
@@ -870,6 +1066,9 @@
         pw.print("mRebootEscrowListener=");
         pw.println(mRebootEscrowListener);
 
+        pw.print("mLoadEscrowDataErrorCode=");
+        pw.println(mLoadEscrowDataErrorCode);
+
         boolean keySet;
         synchronized (mKeyGenerationLock) {
             keySet = mPendingRebootEscrowKey != null;
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 1e0822d..397fdd8 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.Pair;
@@ -147,8 +148,6 @@
             return crossProfileDomainInfos;
         }
 
-        UserInfo userInfo = mUserManagerInternal.getUserInfo(userId);
-
         // Grouping the CrossProfileIntentFilters based on targerId
         SparseArray<List<CrossProfileIntentFilter>> crossProfileIntentFiltersByUser =
                 new SparseArray<>();
@@ -183,11 +182,9 @@
                 continue;
             }
 
-            UserInfo targetUserInfo = mUserManagerInternal.getUserInfo(targetUserId);
-
             // Choosing strategy based on source and target user
             CrossProfileResolver crossProfileResolver =
-                    chooseCrossProfileResolver(computer, userInfo, targetUserInfo);
+                    chooseCrossProfileResolver(computer, userId, targetUserId);
 
         /*
         If {@link CrossProfileResolver} is available for source,target pair we will call it to
@@ -234,23 +231,21 @@
     /**
      * Returns {@link CrossProfileResolver} strategy based on source and target user
      * @param computer {@link Computer} instance used for resolution by {@link ComponentResolverApi}
-     * @param sourceUserInfo source user
-     * @param targetUserInfo target user
+     * @param sourceUserId source user
+     * @param targetUserId target user
      * @return {@code CrossProfileResolver} which has value if source and target have
      * strategy configured otherwise null.
      */
     @SuppressWarnings("unused")
     private CrossProfileResolver chooseCrossProfileResolver(@NonNull Computer computer,
-            UserInfo sourceUserInfo, UserInfo targetUserInfo) {
-        //todo change isCloneProfile to user properties b/241532322
+            @UserIdInt int sourceUserId, @UserIdInt int targetUserId) {
         /**
-         * If source or target user is clone profile, using {@link CloneProfileResolver}
-         * We would allow CloneProfileResolver only if flag
-         * SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE is enabled
+         * If source or target user is clone profile, using {@link NoFilteringResolver}
+         * We would return NoFilteringResolver only if it is allowed(feature flag is set).
          */
-        if (sourceUserInfo.isCloneProfile() || targetUserInfo.isCloneProfile()) {
-            if (CloneProfileResolver.isIntentRedirectionForCloneProfileAllowed()) {
-                return new CloneProfileResolver(computer.getComponentResolver(),
+        if (shouldUseNoFilteringResolver(sourceUserId, targetUserId)) {
+            if (NoFilteringResolver.isIntentRedirectionAllowed()) {
+                return new NoFilteringResolver(computer.getComponentResolver(),
                         mUserManager);
             } else {
                 return null;
@@ -624,7 +619,6 @@
             categorizeResolveInfoByTargetUser, int sourceUserId, int highestApprovalLevel) {
 
         List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
-        UserInfo sourceUserInfo = mUserManagerInternal.getUserInfo(sourceUserId);
 
         for (int index = 0; index < categorizeResolveInfoByTargetUser.size(); index++) {
 
@@ -634,8 +628,8 @@
             } else {
                 // finding cross profile strategy based on source and target user
                 CrossProfileResolver crossProfileIntentResolver =
-                        chooseCrossProfileResolver(computer, sourceUserInfo, mUserManagerInternal
-                                .getUserInfo(categorizeResolveInfoByTargetUser.keyAt(index)));
+                        chooseCrossProfileResolver(computer, sourceUserId,
+                                categorizeResolveInfoByTargetUser.keyAt(index));
                 // if strategy is available call it and add its filtered results
                 if (crossProfileIntentResolver != null) {
                     crossProfileDomainInfos.addAll(crossProfileIntentResolver
@@ -678,4 +672,32 @@
                 && crossProfileDomainInfos.get(0).mResolveInfo != null
                 && crossProfileDomainInfos.get(0).mResolveInfo.priority >= 0;
     }
+
+    /**
+     * Deciding if we need to user {@link NoFilteringResolver} based on source and target user
+     * @param sourceUserId id of initiating user
+     * @param targetUserId id of cross profile linked user
+     * @return true if {@link NoFilteringResolver} is applicable in this case.
+     */
+    private boolean shouldUseNoFilteringResolver(@UserIdInt int sourceUserId,
+            @UserIdInt int targetUserId) {
+        return isNoFilteringPropertyConfiguredForUser(sourceUserId)
+                || isNoFilteringPropertyConfiguredForUser(targetUserId);
+    }
+
+    /**
+     * Check if configure property for cross profile intent resolution strategy for user is
+     * {@link UserProperties#CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING}
+     * @param userId id of user to check for property
+     * @return true if user have property set to
+     * {@link UserProperties#CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING}
+     */
+    private boolean isNoFilteringPropertyConfiguredForUser(@UserIdInt int userId) {
+        if (!mUserManager.isProfile(userId)) return false;
+        UserProperties userProperties = mUserManagerInternal.getUserProperties(userId);
+        if (userProperties == null) return false;
+
+        return userProperties.getCrossProfileIntentResolutionStrategy()
+                == UserProperties.CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/CloneProfileResolver.java b/services/core/java/com/android/server/pm/NoFilteringResolver.java
similarity index 89%
rename from services/core/java/com/android/server/pm/CloneProfileResolver.java
rename to services/core/java/com/android/server/pm/NoFilteringResolver.java
index 73036f1..492f915 100644
--- a/services/core/java/com/android/server/pm/CloneProfileResolver.java
+++ b/services/core/java/com/android/server/pm/NoFilteringResolver.java
@@ -30,9 +30,10 @@
 import java.util.function.Function;
 
 /**
- * Cross Profile intent resolution strategy used for and to clone profile.
+ * Intent resolution strategy used when no filtering is required. As of now, the known use-case is
+ * clone profile.
  */
-public class CloneProfileResolver extends CrossProfileResolver {
+public class NoFilteringResolver extends CrossProfileResolver {
 
     /**
      * Feature flag to allow/restrict intent redirection from/to clone profile.
@@ -48,7 +49,7 @@
      * Returns true if intent redirection for clone profile feature flag is set
      * @return value of flag allow_intent_redirection_for_clone_profile
      */
-    public static boolean isIntentRedirectionForCloneProfileAllowed() {
+    public static boolean isIntentRedirectionAllowed() {
         final long token = Binder.clearCallingIdentity();
         try {
             return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_APP_CLONING,
@@ -58,13 +59,13 @@
         }
     }
 
-    public CloneProfileResolver(ComponentResolverApi componentResolver,
+    public NoFilteringResolver(ComponentResolverApi componentResolver,
             UserManagerService userManagerService) {
         super(componentResolver, userManagerService);
     }
 
     /**
-     * This is resolution strategy for Clone Profile.
+     * This is resolution strategy for when no filtering is required.
      * In case of clone profile, the profile is supposed to be transparent to end user. To end user
      * clone and owner profile should be part of same user space. Hence, the resolution strategy
      * would resolve intent in both profile and return combined result without any filtering of the
@@ -105,8 +106,8 @@
     }
 
     /**
-     * As clone and owner profile are going to be part of the same userspace, we need no filtering
-     * out of any clone profile's result
+     * In case of Clone profile, the clone and owner profile are going to be part of the same
+     * userspace, we need no filtering out of any clone profile's result.
      * @param intent request
      * @param crossProfileDomainInfos resolved in target user
      * @param flags for intent resolution
@@ -119,7 +120,7 @@
     public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(Intent intent,
             List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, int sourceUserId,
             int targetUserId, int highestApprovalLevel) {
-        // no filtering for clone profile
+        // no filtering
         return crossProfileDomainInfos;
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 399e32a..9a98e1e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4285,6 +4285,11 @@
 
         // Prune unused static shared libraries which have been cached a period of time
         schedulePruneUnusedStaticSharedLibraries(false /* delay */);
+
+        DexUseManagerLocal dexUseManager = DexOptHelper.getDexUseManagerLocal();
+        if (dexUseManager != null) {
+            dexUseManager.systemReady();
+        }
     }
 
     //TODO: b/111402650
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 9b337adf..edb2a4be3b 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -133,7 +133,9 @@
                         .setUseParentsContacts(true)
                         .setUpdateCrossProfileIntentFiltersOnOTA(true)
                         .setCrossProfileIntentFilterAccessControl(
-                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM));
+                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+                        .setCrossProfileIntentResolutionStrategy(UserProperties
+                                .CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_NO_FILTERING));
     }
 
     /**
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 863ee75..73b2238 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3610,6 +3610,23 @@
             }
         }
 
+        @Override
+        public void onTvMessage(String type, Bundle data) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onTvMessage(type=" + type + ", data=" + data + ")");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onTvMessage(type, data, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onTvMessage", e);
+                }
+            }
+        }
+
         // For the recording session only
         @Override
         public void onRecordingStopped(Uri recordedProgramUri) {
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 966e883..ebee995 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -1049,6 +1049,29 @@
         }
 
         @Override
+        public void notifyTvMessage(IBinder sessionToken, String type, Bundle data, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyTvMessage");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyTvMessage(type, data);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyTvMessage", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+
+        @Override
         public void notifyRecordingStarted(IBinder sessionToken, String recordingId, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index a16e659..4428be7 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1644,4 +1644,19 @@
         }
         return false;
     }
+
+    @Override
+    public void enableTaskLocaleOverride(IBinder token) {
+        if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
+            // Only allow system to align locale.
+            return;
+        }
+
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+            if (r != null) {
+                r.getTask().mAlignActivityLocaleWithTask = true;
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8410942..9a7b165 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -298,6 +298,7 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.IRemoteCallback;
+import android.os.LocaleList;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.RemoteException;
@@ -7998,6 +7999,9 @@
         }
         super.resolveOverrideConfiguration(newParentConfiguration);
         final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+
+        applyLocaleOverrideIfNeeded(resolvedConfig);
+
         if (isFixedRotationTransforming()) {
             // The resolved configuration is applied with rotated display configuration. If this
             // activity matches its parent (the following resolving procedures are no-op), then it
@@ -10160,6 +10164,35 @@
         }
     }
 
+    private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) {
+        // We always align the locale for ActivityEmbedding apps. System apps or some apps which
+        // has set known cert apps can embed across different uid activity.
+        boolean shouldAlignLocale = isEmbedded()
+                || (task != null && task.mAlignActivityLocaleWithTask);
+        if (!shouldAlignLocale) {
+            return;
+        }
+
+        boolean differentPackage = task != null
+                && task.realActivity != null
+                && !task.realActivity.getPackageName().equals(packageName);
+        if (!differentPackage) {
+            return;
+        }
+
+        LocaleList locale;
+        final ActivityTaskManagerInternal.PackageConfig appConfig =
+                mAtmService.mPackageConfigPersister.findPackageConfiguration(
+                        task.realActivity.getPackageName(), mUserId);
+        // if there is no app locale for the package, clear the target activity's locale.
+        if (appConfig == null || appConfig.mLocales == null || appConfig.mLocales.isEmpty()) {
+            locale = LocaleList.getEmptyLocaleList();
+        } else {
+            locale = appConfig.mLocales;
+        }
+        resolvedConfig.setLocales(locale);
+    }
+
     /**
      * Whether we should send fake focus when the activity is resumed. This is done because some
      * game engines wait to get focus before drawing the content of the app.
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 6b5f068..7bd8c53 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -99,7 +99,7 @@
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
                 return mService.getRecentTasks().createRecentTaskInfo(task,
-                        false /* stripExtras */);
+                        false /* stripExtras */, true /* getTasksAllowed */);
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 659f8d7..49eaea2 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -508,6 +508,9 @@
         }
         mClientVisible = clientVisible;
         updateVisibility();
+        // The visibility change needs a traversal to apply.
+        mDisplayContent.setLayoutNeeded();
+        mDisplayContent.mWmService.mWindowPlacerLocked.requestTraversal();
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9e95918..4be1c83 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -976,7 +976,7 @@
                 continue;
             }
 
-            res.add(createRecentTaskInfo(task, true /* stripExtras */));
+            res.add(createRecentTaskInfo(task, true /* stripExtras */, getTasksAllowed));
         }
         return res;
     }
@@ -1889,7 +1889,8 @@
     /**
      * Creates a new RecentTaskInfo from a Task.
      */
-    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras) {
+    ActivityManager.RecentTaskInfo createRecentTaskInfo(Task tr, boolean stripExtras,
+            boolean getTasksAllowed) {
         final ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
         // If the recent Task is detached, we consider it will be re-attached to the default
         // TaskDisplayArea because we currently only support recent overview in the default TDA.
@@ -1901,6 +1902,9 @@
         rti.id = rti.isRunning ? rti.taskId : INVALID_TASK_ID;
         rti.persistentId = rti.taskId;
         rti.lastSnapshotData.set(tr.mLastTaskSnapshotData);
+        if (!getTasksAllowed) {
+            Task.trimIneffectiveInfo(tr, rti);
+        }
 
         // Fill in organized child task info for the task created by organizer.
         if (tr.mCreatedByOrganizer) {
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 614b405..1cc1a57 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -173,6 +173,10 @@
         }
         // Fill in some deprecated values
         rti.id = rti.taskId;
+
+        if (!mAllowed) {
+            Task.trimIneffectiveInfo(task, rti);
+        }
         return rti;
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3b5b5a9..852c9b2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -612,6 +612,8 @@
 
     boolean mLastSurfaceShowing = true;
 
+    boolean mAlignActivityLocaleWithTask = false;
+
     private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
             Intent _affinityIntent, String _affinity, String _rootAffinity,
             ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -3402,6 +3404,54 @@
         info.isSleeping = shouldSleepActivities();
     }
 
+    /**
+     * Removes the activity info if the activity belongs to a different uid, which is
+     * different from the app that hosts the task.
+     */
+    static void trimIneffectiveInfo(Task task, TaskInfo info) {
+        final ActivityRecord baseActivity = task.getActivity(r -> !r.finishing,
+                false /* traverseTopToBottom */);
+        final int baseActivityUid =
+                baseActivity != null ? baseActivity.getUid() : task.effectiveUid;
+
+        if (info.topActivityInfo != null
+                && task.effectiveUid != info.topActivityInfo.applicationInfo.uid) {
+            // Making a copy to prevent eliminating the info in the original ActivityRecord.
+            info.topActivityInfo = new ActivityInfo(info.topActivityInfo);
+            info.topActivityInfo.applicationInfo =
+                    new ApplicationInfo(info.topActivityInfo.applicationInfo);
+
+            // Strip the sensitive info.
+            info.topActivity = new ComponentName("", "");
+            info.topActivityInfo.packageName = "";
+            info.topActivityInfo.taskAffinity = "";
+            info.topActivityInfo.processName = "";
+            info.topActivityInfo.name = "";
+            info.topActivityInfo.parentActivityName = "";
+            info.topActivityInfo.targetActivity = "";
+            info.topActivityInfo.splitName = "";
+            info.topActivityInfo.applicationInfo.className = "";
+            info.topActivityInfo.applicationInfo.credentialProtectedDataDir = "";
+            info.topActivityInfo.applicationInfo.dataDir = "";
+            info.topActivityInfo.applicationInfo.deviceProtectedDataDir = "";
+            info.topActivityInfo.applicationInfo.manageSpaceActivityName = "";
+            info.topActivityInfo.applicationInfo.nativeLibraryDir = "";
+            info.topActivityInfo.applicationInfo.nativeLibraryRootDir = "";
+            info.topActivityInfo.applicationInfo.processName = "";
+            info.topActivityInfo.applicationInfo.publicSourceDir = "";
+            info.topActivityInfo.applicationInfo.scanPublicSourceDir = "";
+            info.topActivityInfo.applicationInfo.scanSourceDir = "";
+            info.topActivityInfo.applicationInfo.sourceDir = "";
+            info.topActivityInfo.applicationInfo.taskAffinity = "";
+            info.topActivityInfo.applicationInfo.name = "";
+            info.topActivityInfo.applicationInfo.packageName = "";
+        }
+
+        if (task.effectiveUid != baseActivityUid) {
+            info.baseActivity = new ComponentName("", "");
+        }
+    }
+
     @Nullable PictureInPictureParams getPictureInPictureParams() {
         final Task topTask = getTopMostTask();
         if (topTask == null) return null;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 7d15902..aac5ef6 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1966,6 +1966,13 @@
                     creationParams.getPairedPrimaryFragmentToken());
             final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
             position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
+        } else if (creationParams.getPairedActivityToken() != null) {
+            // When there is a paired Activity, we want to place the new TaskFragment right above
+            // the paired Activity to make sure the Activity position is not changed after reparent.
+            final ActivityRecord pairedActivity = ActivityRecord.forTokenLocked(
+                    creationParams.getPairedActivityToken());
+            final int pairedPosition = ownerTask.mChildren.indexOf(pairedActivity);
+            position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
         } else {
             position = POSITION_TOP;
         }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5b9460a..5ebf6ce 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1250,7 +1250,7 @@
         // DexUseManagerLocal needs to be loaded after PackageManagerLocal has been registered, but
         // before PackageManagerService starts processing binder calls to notifyDexLoad.
         LocalManagerRegistry.addManager(
-                DexUseManagerLocal.class, DexUseManagerLocal.createInstance());
+                DexUseManagerLocal.class, DexUseManagerLocal.createInstance(mSystemContext));
         t.traceEnd();
 
         if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index f3ad6d8..450cc40 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -35,6 +35,7 @@
             startWithParent='false'
             useParentsContacts='false'
             crossProfileIntentFilterAccessControl='20'
+            crossProfileIntentResolutionStrategy='0'
         />
     </profile-type>
     <profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 20beed0..99f7905 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -41,7 +41,6 @@
 import android.hardware.biometrics.fingerprint.ISession;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.Fingerprint;
-import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -55,7 +54,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.log.CallbackWithProbe;
@@ -398,274 +396,6 @@
         verify(mCancellationSignal).cancel();
     }
 
-    @Test
-    public void fingerprintPowerIgnoresAuthInWindow() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-        when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        client.onPowerPressed();
-        client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        mLooper.moveTimeForward(1000);
-        mLooper.dispatchAll();
-
-        verify(mCallback).onClientFinished(any(), eq(false));
-        verify(mCancellationSignal).cancel();
-    }
-
-    @Test
-    public void fingerprintAuthIgnoredWaitingForPower() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-        when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        client.onAuthenticated(new Fingerprint("friendly", 3 /* fingerId */, 4 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        client.onPowerPressed();
-        mLooper.moveTimeForward(1000);
-        mLooper.dispatchAll();
-
-        verify(mCallback).onClientFinished(any(), eq(false));
-        verify(mCancellationSignal).cancel();
-    }
-
-    @Test
-    public void fingerprintAuthFailsWhenAuthAfterPower() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-        when(mHal.authenticate(anyLong())).thenReturn(mCancellationSignal);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        client.onPowerPressed();
-        mLooper.dispatchAll();
-        mLooper.moveTimeForward(1000);
-        mLooper.dispatchAll();
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        mLooper.dispatchAll();
-        mLooper.moveTimeForward(1000);
-        mLooper.dispatchAll();
-
-        verify(mCallback, never()).onClientFinished(any(), eq(true));
-        verify(mCallback).onClientFinished(any(), eq(false));
-        when(mHal.authenticateWithContext(anyLong(), any())).thenReturn(mCancellationSignal);
-    }
-
-    @Test
-    public void sideFingerprintDoesntSendAuthImmediately() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        mLooper.dispatchAll();
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        mLooper.dispatchAll();
-
-        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-    }
-
-    @Test
-    public void sideFingerprintSkipsWindowIfFingerUp() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        mLooper.dispatchAll();
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        client.onAcquired(FINGER_UP, 0);
-        mLooper.dispatchAll();
-
-        verify(mCallback).onClientFinished(any(), eq(true));
-    }
-
-    @Test
-    public void sideFingerprintSkipsWindowIfVendorMessageMatch() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-        final int vendorAcquireMessage = 1234;
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
-                FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
-                vendorAcquireMessage);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        mLooper.dispatchAll();
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, vendorAcquireMessage);
-        mLooper.dispatchAll();
-
-        verify(mCallback).onClientFinished(any(), eq(true));
-    }
-
-    @Test
-    public void sideFingerprintDoesNotSkipWindowOnVendorErrorMismatch() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-        final int vendorAcquireMessage = 1234;
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage,
-                FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsSkipWaitForPowerVendorAcquireMessage,
-                vendorAcquireMessage);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        mLooper.dispatchAll();
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 1);
-        mLooper.dispatchAll();
-
-        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-    }
-
-    @Test
-    public void sideFingerprintSendsAuthIfFingerUp() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        mLooper.dispatchAll();
-        client.onAcquired(FINGER_UP, 0);
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        mLooper.dispatchAll();
-
-        verify(mCallback).onClientFinished(any(), eq(true));
-    }
-
-    @Test
-    public void sideFingerprintShortCircuitExpires() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
-        final int timeBeforeAuthSent = 500;
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsKeyguardPowerPressWindow, timeBeforeAuthSent);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsSkipWaitForPowerAcquireMessage, FINGER_UP);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        mLooper.dispatchAll();
-        client.onAcquired(FINGER_UP, 0);
-        mLooper.dispatchAll();
-
-        mLooper.moveTimeForward(500);
-        mLooper.dispatchAll();
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        mLooper.dispatchAll();
-        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
-        mLooper.moveTimeForward(500);
-        mLooper.dispatchAll();
-        verify(mCallback).onClientFinished(any(), eq(true));
-    }
-
-    @Test
-    public void sideFingerprintPowerWindowStartsOnAcquireStart() throws Exception {
-        final int powerWindow = 500;
-        final long authStart = 300;
-
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-
-        // Acquire start occurs at time = 0ms
-        when(mClock.millis()).thenReturn(0L);
-        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
-
-        // Auth occurs at time = 300
-        when(mClock.millis()).thenReturn(authStart);
-        // At this point the delay should be 500 - (300 - 0) == 200 milliseconds.
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        mLooper.dispatchAll();
-        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
-        // After waiting 200 milliseconds, auth should succeed.
-        mLooper.moveTimeForward(powerWindow - authStart);
-        mLooper.dispatchAll();
-        verify(mCallback).onClientFinished(any(), eq(true));
-    }
-
-    @Test
-    public void sideFingerprintPowerWindowStartsOnLastAcquireStart() throws Exception {
-        final int powerWindow = 500;
-
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.config_sidefpsBpPowerPressWindow, powerWindow);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-        // Acquire start occurs at time = 0ms
-        when(mClock.millis()).thenReturn(0L);
-        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
-
-        // Auth reject occurs at time = 300ms
-        when(mClock.millis()).thenReturn(300L);
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                false /* authenticated */, new ArrayList<>());
-        mLooper.dispatchAll();
-
-        mLooper.moveTimeForward(300);
-        mLooper.dispatchAll();
-        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
-        when(mClock.millis()).thenReturn(1300L);
-        client.onAcquired(FingerprintManager.FINGERPRINT_ACQUIRED_START, 0 /* vendorCode */);
-
-        // If code is correct, the new acquired start timestamp should be used
-        // and the code should only have to wait 500 - (1500-1300)ms.
-        when(mClock.millis()).thenReturn(1500L);
-        client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
-                true /* authenticated */, new ArrayList<>());
-        mLooper.dispatchAll();
-
-        mLooper.moveTimeForward(299);
-        mLooper.dispatchAll();
-        verify(mCallback, never()).onClientFinished(any(), anyBoolean());
-
-        mLooper.moveTimeForward(1);
-        mLooper.dispatchAll();
-        verify(mCallback).onClientFinished(any(), eq(true));
-    }
-
-    @Test
-    public void sideFpsPowerPressCancelsIsntantly() throws Exception {
-        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
-
-        final FingerprintAuthenticationClient client = createClient(1);
-        client.start(mCallback);
-
-        client.onPowerPressed();
-        mLooper.dispatchAll();
-
-        verify(mCallback, never()).onClientFinished(any(), eq(true));
-        verify(mCallback).onClientFinished(any(), eq(false));
-    }
-
     private FingerprintAuthenticationClient createClient() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
     }
diff --git a/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/OWNERS b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/OWNERS
new file mode 100644
index 0000000..008a53f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/datatransfer/contextsync/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
index 112db76..ad1ecf1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java
@@ -88,6 +88,7 @@
         Looper looper = mTestLooper.getLooper();
         mHdmiControlService.setIoLooper(looper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
index e4eecc6..3df0449 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java
@@ -105,6 +105,7 @@
         Looper looper = mTestLooper.getLooper();
         hdmiControlService.setIoLooper(looper);
         hdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+        hdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 2cb46da..61ab99b 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -101,6 +101,7 @@
         Looper looper = mTestLooper.getLooper();
         hdmiControlService.setIoLooper(looper);
         hdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+        hdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 hdmiControlService, mNativeWrapper, hdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
index 8ff87e3..93b151e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeControlTest.java
@@ -154,6 +154,7 @@
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
                 HdmiControlManager.VOLUME_CONTROL_DISABLED);
         mHdmiControlService.setHdmiCecConfig(mHdmiCecConfig);
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
 
         mNativeWrapper = new FakeNativeWrapper();
         mNativeWrapper.setPhysicalAddress(getPhysicalAddress());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
index 3a57db9..c4c5c2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java
@@ -104,6 +104,7 @@
         Looper looper = mTestLooper.getLooper();
         mHdmiControlService.setIoLooper(looper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
index 6a899e8..b571f43 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java
@@ -120,6 +120,7 @@
 
         mHdmiControlService.setIoLooper(mMyLooper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
index 0419768..4d8d25a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java
@@ -129,6 +129,7 @@
 
         mHdmiControlService.setIoLooper(mMyLooper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeDeviceConfigWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeDeviceConfigWrapper.java
new file mode 100644
index 0000000..8780329
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeDeviceConfigWrapper.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.provider.DeviceConfig;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Fake class which stubs DeviceConfigWrapper (useful for testing).
+ */
+public class FakeDeviceConfigWrapper extends DeviceConfigWrapper {
+
+    // Set all boolean flags to true such that all unit tests are running with enabled features.
+    @Override
+    boolean getBoolean(String name, boolean defaultValue) {
+        return true;
+    }
+
+    @Override
+    void addOnPropertiesChangedListener(Executor mainExecutor,
+            DeviceConfig.OnPropertiesChangedListener onPropertiesChangedListener) {
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index d2fe6da..f27587e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -114,6 +114,7 @@
         mHdmiCecNetwork = new HdmiCecNetwork(mHdmiControlServiceSpy,
                 mHdmiCecController, mHdmiMhlControllerStub);
         mHdmiControlServiceSpy.setHdmiCecNetwork(mHdmiCecNetwork);
+        mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
 
         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
         hdmiPortInfos[0] =
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
index 367f41d..a7232fe 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java
@@ -108,6 +108,7 @@
         doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion();
         doNothing().when(mHdmiControlServiceSpy)
                 .writeStringSystemProperty(anyString(), anyString());
+        mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
 
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index de2c218..90acc99 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -175,6 +175,7 @@
                 HdmiControlManager.VOLUME_CONTROL_ENABLED);
         mMyLooper = mTestLooper.getLooper();
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
         mHdmiCecLocalDevicePlayback = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 3ed8983..dfab207 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -141,6 +141,7 @@
                 };
 
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mHdmiControlService.setIoLooper(mMyLooper);
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index b30118c..3796ce9 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -179,6 +179,7 @@
                 };
         mHdmiControlService.setIoLooper(mTestLooper.getLooper());
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 5dd29fd..233fd6e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -180,6 +180,7 @@
 
         mHdmiControlService.setIoLooper(mMyLooper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
index 4e5336e..8e5bb13 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java
@@ -91,6 +91,7 @@
 
         mHdmiControlService.setIoLooper(myLooper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(contextSpy));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index aa49a62..6c77405 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -119,6 +119,7 @@
 
         mHdmiControlServiceSpy.setIoLooper(mMyLooper);
         mHdmiControlServiceSpy.setHdmiCecConfig(hdmiCecConfig);
+        mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
         mHdmiControlServiceSpy.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
 
         mNativeWrapper = new FakeNativeWrapper();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
index bf44e09..c79e219 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
@@ -121,6 +121,7 @@
 
         mHdmiControlService.setIoLooper(mMyLooper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 9b8cedf..b0e8ca7 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -120,6 +120,7 @@
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         mHdmiControlService.initService();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
index f72ac71..a623841 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java
@@ -93,6 +93,7 @@
         Looper looper = mTestLooper.getLooper();
         mHdmiControlService.setIoLooper(looper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 this.mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
index f719ca1..1c19341 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java
@@ -116,6 +116,7 @@
 
         mHdmiControlService.setIoLooper(mMyLooper);
         mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
index be62df8..4e8cf4a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java
@@ -175,6 +175,7 @@
         mNativeWrapper = new FakeNativeWrapper();
         mHdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mHdmiControlService.setCecController(mHdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[1];
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
index e3c8939..cac7815 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java
@@ -89,6 +89,7 @@
         mLooper = mTestLooper.getLooper();
         mHdmiControlServiceSpy.setIoLooper(mLooper);
         mHdmiControlServiceSpy.setHdmiCecConfig(new FakeHdmiCecConfig(mContextSpy));
+        mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper());
 
         mNativeWrapper = new FakeNativeWrapper();
         mNativeWrapper.setPhysicalAddress(mPhysicalAddress);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
index e7557fe..70f9e5c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java
@@ -97,6 +97,7 @@
         mNativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         mHdmiControlService.setCecController(hdmiCecController);
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
index c2f706a..b13ef4f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java
@@ -168,6 +168,7 @@
         Looper looper = mTestLooper.getLooper();
         hdmiControlService.setIoLooper(looper);
         hdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        hdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
         HdmiCecController.NativeWrapper nativeWrapper = new FakeNativeWrapper();
         HdmiCecController hdmiCecController = HdmiCecController.createWithNativeWrapper(
                 hdmiControlService, nativeWrapper, hdmiControlService.getAtomWriter());
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
index c22782c..705a5da 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
@@ -22,6 +22,7 @@
 import android.hardware.input.InputManager
 import android.os.test.TestLooper
 import android.platform.test.annotations.Presubmit
+import android.provider.Settings
 import android.view.InputDevice
 import android.view.KeyEvent
 import androidx.test.core.app.ApplicationProvider
@@ -113,38 +114,75 @@
     }
 
     @Test
-    fun testKeyRemapping() {
-        val keyboard = createKeyboard(DEVICE_ID)
-        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+    fun testKeyRemapping_whenRemappingEnabled() {
+        ModifierRemappingFlag(true).use {
+            val keyboard = createKeyboard(DEVICE_ID)
+            Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
 
-        for (i in REMAPPABLE_KEYS.indices) {
-            val fromKeyCode = REMAPPABLE_KEYS[i]
-            val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
-            mKeyRemapper.remapKey(fromKeyCode, toKeyCode)
+            for (i in REMAPPABLE_KEYS.indices) {
+                val fromKeyCode = REMAPPABLE_KEYS[i]
+                val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+                mKeyRemapper.remapKey(fromKeyCode, toKeyCode)
+                testLooper.dispatchNext()
+            }
+
+            val remapping = mKeyRemapper.keyRemapping
+            val expectedSize = REMAPPABLE_KEYS.size
+            assertEquals("Remapping size should be $expectedSize", expectedSize, remapping.size)
+
+            for (i in REMAPPABLE_KEYS.indices) {
+                val fromKeyCode = REMAPPABLE_KEYS[i]
+                val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+                assertEquals(
+                    "Remapping should include mapping from $fromKeyCode to $toKeyCode",
+                    toKeyCode,
+                    remapping.getOrDefault(fromKeyCode, -1)
+                )
+            }
+
+            mKeyRemapper.clearAllKeyRemappings()
             testLooper.dispatchNext()
-        }
 
-        val remapping = mKeyRemapper.keyRemapping
-        val expectedSize = REMAPPABLE_KEYS.size
-        assertEquals("Remapping size should be $expectedSize", expectedSize, remapping.size)
-
-        for (i in REMAPPABLE_KEYS.indices) {
-            val fromKeyCode = REMAPPABLE_KEYS[i]
-            val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
             assertEquals(
-                "Remapping should include mapping from $fromKeyCode to $toKeyCode",
-                toKeyCode,
-                remapping.getOrDefault(fromKeyCode, -1)
+                "Remapping size should be 0 after clearAllModifierKeyRemappings",
+                0,
+                mKeyRemapper.keyRemapping.size
+            )
+        }
+    }
+
+    @Test
+    fun testKeyRemapping_whenRemappingDisabled() {
+        ModifierRemappingFlag(false).use {
+            val keyboard = createKeyboard(DEVICE_ID)
+            Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+
+            mKeyRemapper.remapKey(REMAPPABLE_KEYS[0], REMAPPABLE_KEYS[1])
+            testLooper.dispatchAll()
+
+            val remapping = mKeyRemapper.keyRemapping
+            assertEquals(
+                "Remapping should not be done if modifier key remapping is disabled",
+                0,
+                remapping.size
+            )
+        }
+    }
+
+    private inner class ModifierRemappingFlag constructor(enabled: Boolean) : AutoCloseable {
+        init {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_modifier_key", enabled.toString()
             )
         }
 
-        mKeyRemapper.clearAllKeyRemappings()
-        testLooper.dispatchNext()
-
-        assertEquals(
-            "Remapping size should be 0 after clearAllModifierKeyRemappings",
-            0,
-            mKeyRemapper.keyRemapping.size
-        )
+        override fun close() {
+            Settings.Global.putString(
+                context.contentResolver,
+                "settings_new_keyboard_modifier_key",
+                ""
+            )
+        }
     }
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index 858f658..d9d0715 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -50,6 +50,8 @@
 import android.content.ContextWrapper;
 import android.content.pm.UserInfo;
 import android.hardware.rebootescrow.IRebootEscrow;
+import android.net.ConnectivityManager;
+import android.net.Network;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteException;
@@ -72,6 +74,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
@@ -113,6 +116,7 @@
     private RebootEscrowManager mService;
     private SecretKey mAesKey;
     private MockInjector mMockInjector;
+    private Handler mHandler;
 
     public interface MockableRebootEscrowInjected {
         int getBootCount();
@@ -131,6 +135,9 @@
         private final RebootEscrowKeyStoreManager mKeyStoreManager;
         private boolean mServerBased;
         private RebootEscrowProviderInterface mRebootEscrowProviderInUse;
+        private ConnectivityManager.NetworkCallback mNetworkCallback;
+        private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer;
+        private boolean mWaitForInternet;
 
         MockInjector(Context context, UserManager userManager,
                 IRebootEscrow rebootEscrow,
@@ -140,6 +147,7 @@
             super(context, storage);
             mRebootEscrow = rebootEscrow;
             mServerBased = false;
+            mWaitForInternet = false;
             RebootEscrowProviderHalImpl.Injector halInjector =
                     new RebootEscrowProviderHalImpl.Injector() {
                         @Override
@@ -161,6 +169,7 @@
             super(context, storage);
             mRebootEscrow = null;
             mServerBased = true;
+            mWaitForInternet = false;
             RebootEscrowProviderServerBasedImpl.Injector injector =
                     new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection) {
                         @Override
@@ -196,11 +205,33 @@
         }
 
         @Override
+        public boolean waitForInternet() {
+            return mWaitForInternet;
+        }
+
+        public void setWaitForNetwork(boolean waitForNetworkEnabled) {
+            mWaitForInternet = waitForNetworkEnabled;
+        }
+
+        @Override
         public boolean isNetworkConnected() {
             return false;
         }
 
         @Override
+        public boolean requestNetworkWithInternet(
+                ConnectivityManager.NetworkCallback networkCallback) {
+            mNetworkCallback = networkCallback;
+            mNetworkConsumer.accept(networkCallback);
+            return true;
+        }
+
+        @Override
+        public void stopRequestingNetwork(ConnectivityManager.NetworkCallback networkCallback) {
+            mNetworkCallback = null;
+        }
+
+        @Override
         public RebootEscrowProviderInterface createRebootEscrowProviderIfNeeded() {
             mRebootEscrowProviderInUse = mDefaultRebootEscrowProvider;
             return mRebootEscrowProviderInUse;
@@ -239,6 +270,12 @@
         }
 
         @Override
+        public int getLoadEscrowTimeoutMillis() {
+            // Timeout in 3 seconds.
+            return 3000;
+        }
+
+        @Override
         public String getVbmetaDigest(boolean other) {
             return other ? "" : "fake digest";
         }
@@ -288,6 +325,9 @@
         mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow,
                 mKeyStoreManager, mStorage, mInjected);
         mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage);
+        HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
     }
 
     private void setServerBasedRebootEscrowProvider() throws Exception {
@@ -459,7 +499,7 @@
 
     @Test
     public void loadRebootEscrowDataIfAvailable_NothingAvailable_Success() throws Exception {
-        mService.loadRebootEscrowDataIfAvailable(null);
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
     }
 
     @Test
@@ -496,7 +536,7 @@
                 eq(20), eq(0) /* vbmeta status */, anyInt());
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> keyByteCaptor.getValue());
 
-        mService.loadRebootEscrowDataIfAvailable(null);
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
         verify(mRebootEscrow).retrieveKey();
         assertTrue(metricsSuccessCaptor.getValue());
         verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
@@ -528,9 +568,16 @@
         // pretend reboot happens here
         when(mInjected.getBootCount()).thenReturn(1);
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
-        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
-                eq(0) /* error code */, eq(2) /* Server based */, eq(1) /* attempt count */,
-                anyInt(), eq(0) /* vbmeta status */, anyInt());
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        eq(0) /* error code */,
+                        eq(2) /* Server based */,
+                        eq(1) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
 
         when(mServiceConnection.unwrap(any(), anyLong()))
                 .thenAnswer(invocation -> invocation.getArgument(0));
@@ -566,15 +613,23 @@
         when(mInjected.getBootCount()).thenReturn(1);
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
-        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
-                metricsErrorCodeCaptor.capture(), eq(2) /* Server based */,
-                eq(1) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt());
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(1) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
 
         when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class);
         mService.loadRebootEscrowDataIfAvailable(null);
         verify(mServiceConnection).unwrap(any(), anyLong());
         assertFalse(metricsSuccessCaptor.getValue());
-        assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
+        assertEquals(
+                Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
                 metricsErrorCodeCaptor.getValue());
     }
 
@@ -603,18 +658,24 @@
         when(mInjected.getBootCount()).thenReturn(1);
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
         ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
-        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
-                metricsErrorCodeCaptor.capture(), eq(2) /* Server based */,
-                eq(2) /* attempt count */, anyInt(), eq(0) /* vbmeta status */, anyInt());
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(2) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
         when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(IOException.class);
 
-        HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
-        thread.start();
-        mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper()));
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
         // Sleep 5s for the retry to complete
         Thread.sleep(5 * 1000);
         assertFalse(metricsSuccessCaptor.getValue());
-        assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_NO_NETWORK),
+        assertEquals(
+                Integer.valueOf(RebootEscrowManager.ERROR_NO_NETWORK),
                 metricsErrorCodeCaptor.getValue());
     }
 
@@ -642,16 +703,22 @@
         // pretend reboot happens here
         when(mInjected.getBootCount()).thenReturn(1);
         ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
-        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture(),
-                anyInt(), anyInt(), eq(2) /* attempt count */, anyInt(), anyInt(), anyInt());
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        anyInt(),
+                        anyInt(),
+                        eq(2) /* attempt count */,
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
 
         when(mServiceConnection.unwrap(any(), anyLong()))
                 .thenThrow(new IOException())
                 .thenAnswer(invocation -> invocation.getArgument(0));
 
-        HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
-        thread.start();
-        mService.loadRebootEscrowDataIfAvailable(new Handler(thread.getLooper()));
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
         // Sleep 5s for the retry to complete
         Thread.sleep(5 * 1000);
         verify(mServiceConnection, times(2)).unwrap(any(), anyLong());
@@ -660,6 +727,447 @@
     }
 
     @Test
+    public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternet_success()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        eq(0) /* error code */,
+                        eq(2) /* Server based */,
+                        eq(1) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
+
+        // load escrow data
+        when(mServiceConnection.unwrap(any(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        Network mockNetwork = mock(Network.class);
+        mMockInjector.mNetworkConsumer =
+                (callback) -> {
+                    callback.onAvailable(mockNetwork);
+                };
+
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        verify(mServiceConnection).unwrap(any(), anyLong());
+        assertTrue(metricsSuccessCaptor.getValue());
+        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_serverBasedWaitForInternetRemoteException_Failure()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(1) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
+
+        // load escrow data
+        when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(RemoteException.class);
+        Network mockNetwork = mock(Network.class);
+        mMockInjector.mNetworkConsumer =
+                (callback) -> {
+                    callback.onAvailable(mockNetwork);
+                };
+
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        verify(mServiceConnection).unwrap(any(), anyLong());
+        assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(
+                Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
+                metricsErrorCodeCaptor.getValue());
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_waitForInternet_networkUnavailable()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(0) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
+
+        // Network is not available within timeout.
+        mMockInjector.mNetworkConsumer = ConnectivityManager.NetworkCallback::onUnavailable;
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(
+                Integer.valueOf(RebootEscrowManager.ERROR_NO_NETWORK),
+                metricsErrorCodeCaptor.getValue());
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_waitForInternet_networkLost() throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(2) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
+
+        // Network is available, then lost.
+        when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(new IOException());
+        Network mockNetwork = mock(Network.class);
+        mMockInjector.mNetworkConsumer =
+                (callback) -> {
+                    callback.onAvailable(mockNetwork);
+                    callback.onLost(mockNetwork);
+                };
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        // Sleep 5s for the retry to complete
+        Thread.sleep(5 * 1000);
+        assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(
+                Integer.valueOf(RebootEscrowManager.ERROR_NO_NETWORK),
+                metricsErrorCodeCaptor.getValue());
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_waitForInternet_networkAvailableWithDelay()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(1) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
+
+        // load escrow data
+        when(mServiceConnection.unwrap(any(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        // network available after 1 sec
+        Network mockNetwork = mock(Network.class);
+        mMockInjector.mNetworkConsumer =
+                (callback) -> {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                    callback.onAvailable(mockNetwork);
+                };
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        verify(mServiceConnection).unwrap(any(), anyLong());
+        assertTrue(metricsSuccessCaptor.getValue());
+        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_waitForInternet_timeoutExhausted()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(1) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
+
+        // load reboot escrow data
+        when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(IOException.class);
+        Network mockNetwork = mock(Network.class);
+        // wait past timeout
+        mMockInjector.mNetworkConsumer =
+                (callback) -> {
+                    try {
+                        Thread.sleep(3500);
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                    callback.onAvailable(mockNetwork);
+                };
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        verify(mServiceConnection).unwrap(any(), anyLong());
+        assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(
+                Integer.valueOf(RebootEscrowManager.ERROR_TIMEOUT_EXHAUSTED),
+                metricsErrorCodeCaptor.getValue());
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_serverBasedWaitForNetwork_retryCountExhausted()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        metricsErrorCodeCaptor.capture(),
+                        eq(2) /* Server based */,
+                        eq(2) /* attempt count */,
+                        anyInt(),
+                        eq(0) /* vbmeta status */,
+                        anyInt());
+
+        when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(new IOException());
+        Network mockNetwork = mock(Network.class);
+        mMockInjector.mNetworkConsumer =
+                (callback) -> {
+                    callback.onAvailable(mockNetwork);
+                };
+
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        // Sleep 5s for the retry to complete
+        Thread.sleep(5 * 1000);
+        verify(mServiceConnection, times(2)).unwrap(any(), anyLong());
+        assertFalse(metricsSuccessCaptor.getValue());
+        assertEquals(
+                Integer.valueOf(RebootEscrowManager.ERROR_RETRY_COUNT_EXHAUSTED),
+                metricsErrorCodeCaptor.getValue());
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
+    public void loadRebootEscrowDataIfAvailable_ServerBasedWaitForInternet_RetrySuccess()
+            throws Exception {
+        setServerBasedRebootEscrowProvider();
+        mMockInjector.setWaitForNetwork(true);
+
+        when(mInjected.getBootCount()).thenReturn(0);
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mServiceConnection);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+        // Use x -> x for both wrap & unwrap functions.
+        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+        assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+        // pretend reboot happens here
+        when(mInjected.getBootCount()).thenReturn(1);
+        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+        doNothing()
+                .when(mInjected)
+                .reportMetric(
+                        metricsSuccessCaptor.capture(),
+                        anyInt(),
+                        anyInt(),
+                        eq(2) /* attempt count */,
+                        anyInt(),
+                        anyInt(),
+                        anyInt());
+
+        when(mServiceConnection.unwrap(any(), anyLong()))
+                .thenThrow(new IOException())
+                .thenAnswer(invocation -> invocation.getArgument(0));
+        Network mockNetwork = mock(Network.class);
+        mMockInjector.mNetworkConsumer =
+                (callback) -> {
+                    callback.onAvailable(mockNetwork);
+                };
+
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
+        // Sleep 5s for the retry to complete
+        Thread.sleep(5 * 1000);
+        verify(mServiceConnection, times(2)).unwrap(any(), anyLong());
+        assertTrue(metricsSuccessCaptor.getValue());
+        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
+        assertNull(mMockInjector.mNetworkCallback);
+    }
+
+    @Test
     public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception {
         when(mInjected.getBootCount()).thenReturn(0);
 
@@ -684,7 +1192,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
 
-        mService.loadRebootEscrowDataIfAvailable(null);
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
         verify(mRebootEscrow).retrieveKey();
         verify(mInjected, never()).reportMetric(anyBoolean(), anyInt(), anyInt(), anyInt(),
                 anyInt(), anyInt(), anyInt());
@@ -712,7 +1220,7 @@
         when(mInjected.getBootCount()).thenReturn(10);
         when(mRebootEscrow.retrieveKey()).thenReturn(new byte[32]);
 
-        mService.loadRebootEscrowDataIfAvailable(null);
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
         verify(mInjected, never()).reportMetric(anyBoolean(), anyInt(), anyInt(), anyInt(),
                 anyInt(), anyInt(), anyInt());
     }
@@ -750,7 +1258,7 @@
         // Trigger a vbmeta digest mismatch
         mStorage.setString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
                 "non sense value", USER_SYSTEM);
-        mService.loadRebootEscrowDataIfAvailable(null);
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
         verify(mInjected).reportMetric(eq(true), eq(0) /* error code */, eq(1) /* HAL based */,
                 eq(1) /* attempt count */, anyInt(), eq(2) /* vbmeta status */, anyInt());
         assertEquals(mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_KEY_VBMETA_DIGEST,
@@ -787,7 +1295,7 @@
                 eq(1) /* attempt count */, anyInt(), anyInt(), anyInt());
 
         when(mRebootEscrow.retrieveKey()).thenAnswer(invocation -> null);
-        mService.loadRebootEscrowDataIfAvailable(null);
+        mService.loadRebootEscrowDataIfAvailable(mHandler);
         verify(mRebootEscrow).retrieveKey();
         assertFalse(metricsSuccessCaptor.getValue());
         assertEquals(Integer.valueOf(RebootEscrowManager.ERROR_LOAD_ESCROW_KEY),
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 9625188..26d0ddf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -63,6 +63,7 @@
                 .setInheritDevicePolicy(67)
                 .setUseParentsContacts(false)
                 .setCrossProfileIntentFilterAccessControl(10)
+                .setCrossProfileIntentResolutionStrategy(0)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
@@ -70,6 +71,7 @@
         actualProps.setInheritDevicePolicy(51);
         actualProps.setUseParentsContacts(true);
         actualProps.setCrossProfileIntentFilterAccessControl(20);
+        actualProps.setCrossProfileIntentResolutionStrategy(1);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -154,6 +156,8 @@
                 copy::getInheritDevicePolicy, exposeAll);
         assertEqualGetterOrThrows(orig::getCrossProfileIntentFilterAccessControl,
                 copy::getCrossProfileIntentFilterAccessControl, exposeAll);
+        assertEqualGetterOrThrows(orig::getCrossProfileIntentResolutionStrategy,
+                copy::getCrossProfileIntentResolutionStrategy, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
         assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
@@ -209,5 +213,7 @@
         assertThat(expected.getUseParentsContacts()).isEqualTo(actual.getUseParentsContacts());
         assertThat(expected.getCrossProfileIntentFilterAccessControl())
                 .isEqualTo(actual.getCrossProfileIntentFilterAccessControl());
+        assertThat(expected.getCrossProfileIntentResolutionStrategy())
+                .isEqualTo(actual.getCrossProfileIntentResolutionStrategy());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 1151222..928c6ef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -85,7 +85,8 @@
         final UserProperties.Builder userProps = new UserProperties.Builder()
                 .setShowInLauncher(17)
                 .setUseParentsContacts(true)
-                .setCrossProfileIntentFilterAccessControl(10);
+                .setCrossProfileIntentFilterAccessControl(10)
+                .setCrossProfileIntentResolutionStrategy(1);
         final UserTypeDetails type = new UserTypeDetails.Builder()
                 .setName("a.name")
                 .setEnabled(1)
@@ -145,6 +146,8 @@
         assertTrue(type.getDefaultUserPropertiesReference().getUseParentsContacts());
         assertEquals(10, type.getDefaultUserPropertiesReference()
                 .getCrossProfileIntentFilterAccessControl());
+        assertEquals(1, type.getDefaultUserPropertiesReference()
+                .getCrossProfileIntentResolutionStrategy());
 
         assertEquals(23, type.getBadgeLabel(0));
         assertEquals(24, type.getBadgeLabel(1));
@@ -191,6 +194,8 @@
         assertEquals(UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_ALL,
                 props.getCrossProfileIntentFilterAccessControl());
         assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
+        assertEquals(UserProperties.CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT,
+                props.getCrossProfileIntentResolutionStrategy());
 
         assertFalse(type.hasBadge());
     }
@@ -273,7 +278,8 @@
                 .setShowInLauncher(19)
                 .setStartWithParent(true)
                 .setUseParentsContacts(true)
-                .setCrossProfileIntentFilterAccessControl(10);
+                .setCrossProfileIntentFilterAccessControl(10)
+                .setCrossProfileIntentResolutionStrategy(1);
         final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
         builders.put(userTypeAosp1, new UserTypeDetails.Builder()
                 .setName(userTypeAosp1)
@@ -301,6 +307,8 @@
         assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
         assertEquals(10, aospType.getDefaultUserPropertiesReference()
                 .getCrossProfileIntentFilterAccessControl());
+        assertEquals(1, aospType.getDefaultUserPropertiesReference()
+                .getCrossProfileIntentResolutionStrategy());
         assertTrue(aospType.getDefaultUserPropertiesReference().getStartWithParent());
         assertTrue(aospType.getDefaultUserPropertiesReference()
                 .getUseParentsContacts());
@@ -335,6 +343,8 @@
         assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
         assertEquals(20, aospType.getDefaultUserPropertiesReference()
                 .getCrossProfileIntentFilterAccessControl());
+        assertEquals(0, aospType.getDefaultUserPropertiesReference()
+                .getCrossProfileIntentResolutionStrategy());
         assertFalse(aospType.getDefaultUserPropertiesReference().getStartWithParent());
         assertFalse(aospType.getDefaultUserPropertiesReference()
                 .getUseParentsContacts());
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 5059ef3..b697c76 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -208,6 +208,8 @@
         assertThrows(SecurityException.class, cloneUserProperties::getStartWithParent);
         assertThrows(SecurityException.class,
                 cloneUserProperties::getCrossProfileIntentFilterAccessControl);
+        assertThrows(SecurityException.class,
+                cloneUserProperties::getCrossProfileIntentResolutionStrategy);
 
         // Verify clone user parent
         assertThat(mUserManager.getProfileParent(primaryUserId)).isNull();
@@ -727,6 +729,7 @@
         assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
         assertFalse(userProps.getUseParentsContacts());
         assertThrows(SecurityException.class, userProps::getCrossProfileIntentFilterAccessControl);
+        assertThrows(SecurityException.class, userProps::getCrossProfileIntentResolutionStrategy);
         assertThrows(SecurityException.class, userProps::getStartWithParent);
         assertThrows(SecurityException.class, userProps::getInheritDevicePolicy);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index cdb2642..4d69979 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -39,6 +39,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.spy;
@@ -411,7 +412,14 @@
         final WindowState navBar = createTestWindow("navBar");
 
         getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+
+        waitUntilHandlersIdle();
+        clearInvocations(mDisplayContent);
         getController().getSourceProvider(ITYPE_IME).setClientVisible(true);
+        waitUntilHandlersIdle();
+        // The visibility change should trigger a traversal to notify the change.
+        verify(mDisplayContent).notifyInsetsChanged(any());
+
         getController().getSourceProvider(ITYPE_STATUS_BAR).setWindowContainer(statusBar, null,
                 null);
         getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index d364dbb..74dd361 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE;
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.os.Process.NOBODY_UID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -1207,20 +1208,35 @@
 
     @Test
     public void testCreateRecentTaskInfo_detachedTask() {
-        final Task task = createTaskBuilder(".Task").setCreateActivity(true).build();
+        final Task task = createTaskBuilder(".Task").build();
+        final ComponentName componentName = getUniqueComponentName();
+        new ActivityBuilder(mSupervisor.mService)
+                .setTask(task)
+                .setUid(NOBODY_UID)
+                .setComponent(componentName)
+                .build();
         final TaskDisplayArea tda = task.getDisplayArea();
 
         assertTrue(task.isAttached());
         assertTrue(task.supportsMultiWindow());
 
-        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
+        RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                false /* getTasksAllowed */);
+
+        assertFalse(info.topActivity.equals(componentName));
+        assertFalse(info.topActivityInfo.packageName.equals(componentName.getPackageName()));
+        assertFalse(info.baseActivity.equals(componentName));
+
         // The task can be put in split screen even if it is not attached now.
         task.removeImmediately();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
 
@@ -1229,7 +1245,8 @@
         doReturn(false).when(tda).supportsNonResizableMultiWindow();
         doReturn(false).when(task).isResizeable();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertFalse(info.supportsMultiWindow);
 
@@ -1237,7 +1254,8 @@
         // the device supports it.
         doReturn(true).when(tda).supportsNonResizableMultiWindow();
 
-        info = mRecentTasks.createRecentTaskInfo(task, true);
+        info = mRecentTasks.createRecentTaskInfo(task, true /* stripExtras */,
+                true /* getTasksAllowed */);
 
         assertTrue(info.supportsMultiWindow);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index b70d8bd..656c486 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -811,6 +811,40 @@
     }
 
     @Test
+    public void testApplyTransaction_createTaskFragment_withPairedActivityToken() {
+        final Task task = createTask(mDisplayContent);
+        final ActivityRecord activityAtBottom = createActivityRecord(task);
+        final int uid = Binder.getCallingUid();
+        activityAtBottom.info.applicationInfo.uid = uid;
+        activityAtBottom.getTask().effectiveUid = uid;
+        mTaskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(mFragmentToken)
+                .createActivityCount(1)
+                .build();
+        mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+        final IBinder fragmentToken1 = new Binder();
+        final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+                mOrganizerToken, fragmentToken1, activityAtBottom.token)
+                .setPairedActivityToken(activityAtBottom.token)
+                .build();
+        mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+        mTransaction.createTaskFragment(params);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Successfully created a TaskFragment.
+        final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+                fragmentToken1);
+        assertNotNull(taskFragment);
+        // The new TaskFragment should be positioned right above the paired activity.
+        assertEquals(task.mChildren.indexOf(activityAtBottom) + 1,
+                task.mChildren.indexOf(taskFragment));
+        // The top TaskFragment should remain on top.
+        assertEquals(task.mChildren.indexOf(taskFragment) + 1,
+                task.mChildren.indexOf(mTaskFragment));
+    }
+
+    @Test
     public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
         doReturn(true).when(mTaskFragment).isAttached();
 
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index b8c056e..ca15422 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -540,6 +540,11 @@
 
         /**
          * Creates a builder with the specified {@link PhoneAccountHandle} and label.
+         * <p>
+         * Note: each CharSequence or String field is limited to 256 characters. This check is
+         * enforced when registering the PhoneAccount via
+         * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+         * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
          */
         public Builder(PhoneAccountHandle accountHandle, CharSequence label) {
             this.mAccountHandle = accountHandle;
@@ -570,6 +575,11 @@
 
         /**
          * Sets the label. See {@link PhoneAccount#getLabel()}.
+         * <p>
+         * Note: Each CharSequence or String field is limited to 256 characters. This check is
+         * enforced when registering the PhoneAccount via
+         * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+         * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
          *
          * @param label The label of the phone account.
          * @return The builder.
@@ -636,6 +646,11 @@
 
         /**
          * Sets the short description. See {@link PhoneAccount#getShortDescription}.
+         * <p>
+         * Note: Each CharSequence or String field is limited to 256 characters. This check is
+         * enforced when registering the PhoneAccount via
+         * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+         * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
          *
          * @param value The short description.
          * @return The builder.
@@ -680,6 +695,13 @@
          * <p>
          * {@code PhoneAccount}s only support extra values of type: {@link String}, {@link Integer},
          * and {@link Boolean}.  Extras which are not of these types are ignored.
+         * <p>
+         * Note: Each Bundle (Key, Value) String field is limited to 256 characters. Additionally,
+         * the bundle is limited to 100 (Key, Value) pairs total.  This check is
+         * enforced when registering the PhoneAccount via
+         * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+         * {@link IllegalArgumentException} to be thrown if the character field limit is over 256
+         * or more than 100 (Key, Value) pairs are in the Bundle.
          *
          * @param extras
          * @return
@@ -711,6 +733,11 @@
          * <p>
          * Note: This is an API specific to the Telephony stack; the group Id will be ignored for
          * callers not holding the correct permission.
+         * <p>
+         * Additionally, each CharSequence or String field is limited to 256 characters.
+         * This check is enforced when registering the PhoneAccount via
+         * {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+         * {@link IllegalArgumentException} to be thrown if the character field limit is over 256.
          *
          * @param groupId The group Id of the {@link PhoneAccount} that will replace any other
          * registered {@link PhoneAccount} in Telecom with the same Group Id.
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index ec94f8a..e5db8cf 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -70,6 +70,12 @@
      *           ID provided does not expose personally identifying information.  A
      *           {@link ConnectionService} should use an opaque token as the
      *           {@link PhoneAccountHandle} identifier.
+     * <p>
+     * Note: Each String field is limited to 256 characters. This check is enforced when
+     *           registering the PhoneAccount via
+     *           {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+     *           {@link IllegalArgumentException} to be thrown if the character field limit is
+     *           over 256.
      */
     public PhoneAccountHandle(
             @NonNull ComponentName componentName,
@@ -88,6 +94,13 @@
      *           {@link ConnectionService} should use an opaque token as the
      *           {@link PhoneAccountHandle} identifier.
      * @param userHandle The {@link UserHandle} associated with this {@link PhoneAccountHandle}.
+     *
+     * <p>
+     * Note: Each String field is limited to 256 characters. This check is enforced when
+     *           registering the PhoneAccount via
+     *           {@link TelecomManager#registerPhoneAccount(PhoneAccount)} and will cause an
+     *           {@link IllegalArgumentException} to be thrown if the character field limit is
+     *           over 256.
      */
     public PhoneAccountHandle(
             @NonNull ComponentName componentName,
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index e78a1e1..64bcf71 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -25,6 +25,8 @@
 import android.os.Parcelable;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
 
 import com.android.telephony.Rlog;
 
@@ -247,6 +249,17 @@
     private final List<String> mEmergencyUrns;
     private final int mEmergencyNumberSourceBitmask;
     private final int mEmergencyCallRouting;
+    /**
+     * The source of the EmergencyNumber in the order of precedence.
+     */
+    private static final int[] EMERGENCY_NUMBER_SOURCE_PRECEDENCE;
+    static {
+        EMERGENCY_NUMBER_SOURCE_PRECEDENCE = new int[4];
+        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[0] = EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING;
+        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[1] = EMERGENCY_NUMBER_SOURCE_SIM;
+        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[2] = EMERGENCY_NUMBER_SOURCE_DATABASE;
+        EMERGENCY_NUMBER_SOURCE_PRECEDENCE[3] = EMERGENCY_NUMBER_SOURCE_MODEM_CONFIG;
+    }
 
     /** @hide */
     public EmergencyNumber(@NonNull String number, @NonNull String countryIso, @NonNull String mnc,
@@ -601,19 +614,44 @@
      */
     public static void mergeSameNumbersInEmergencyNumberList(
             List<EmergencyNumber> emergencyNumberList) {
+        mergeSameNumbersInEmergencyNumberList(emergencyNumberList, false);
+    }
+
+    /**
+     * In-place merge same emergency numbers in the emergency number list.
+     *
+     * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’ and 'mnc' fields.
+     * If mergeServiceCategoriesAndUrns is true ignore comparing of 'urns' and
+     * 'categories' fields and determine these fields from most precedent number. Else compare
+     * to get unique combination of EmergencyNumber.
+     * Multiple Emergency Number Sources should be merged into one bitfield for the
+     * same EmergencyNumber.
+     *
+     * @param emergencyNumberList the emergency number list to process
+     * @param mergeServiceCategoriesAndUrns {@code true} determine service category and urns
+     * from most precedent number. {@code false} compare those fields for determing duplicate.
+     *
+     * @hide
+     */
+    public static void mergeSameNumbersInEmergencyNumberList(
+            @NonNull List<EmergencyNumber> emergencyNumberList,
+            boolean mergeServiceCategoriesAndUrns) {
         if (emergencyNumberList == null) {
             return;
         }
+
         Set<Integer> duplicatedEmergencyNumberPosition = new HashSet<>();
         for (int i = 0; i < emergencyNumberList.size(); i++) {
             for (int j = 0; j < i; j++) {
-                if (areSameEmergencyNumbers(
-                        emergencyNumberList.get(i), emergencyNumberList.get(j))) {
-                    Rlog.e(LOG_TAG, "Found unexpected duplicate numbers: "
-                            + emergencyNumberList.get(i) + " vs " + emergencyNumberList.get(j));
+                if (areSameEmergencyNumbers(emergencyNumberList.get(i),
+                        emergencyNumberList.get(j), mergeServiceCategoriesAndUrns)) {
+                    Rlog.e(LOG_TAG, "Found unexpected duplicate numbers "
+                            + emergencyNumberList.get(i)
+                            + " vs " + emergencyNumberList.get(j));
                     // Set the merged emergency number in the current position
-                    emergencyNumberList.set(i, mergeSameEmergencyNumbers(
-                            emergencyNumberList.get(i), emergencyNumberList.get(j)));
+                    emergencyNumberList.set(i,
+                            mergeSameEmergencyNumbers(emergencyNumberList.get(i),
+                            emergencyNumberList.get(j), mergeServiceCategoriesAndUrns));
                     // Mark the emergency number has been merged
                     duplicatedEmergencyNumberPosition.add(j);
                 }
@@ -632,18 +670,24 @@
     /**
      * Check if two emergency numbers are the same.
      *
-     * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’, 'mnc' and
-     * 'categories', and 'routing' fields. Multiple Emergency Number Sources should be
+     * A unique EmergencyNumber has a unique combination of ‘number’, ‘mcc’, 'mnc' fields.
+     * If mergeServiceCategoriesAndUrns is true ignore comparing of 'urns' and
+     * 'categories' fields and determine these fields from most precedent number. Else compare
+     * to get unique combination of EmergencyNumber.
+     * Multiple Emergency Number Sources should be
      * merged into one bitfield for the same EmergencyNumber.
      *
      * @param first first EmergencyNumber to compare
      * @param second second EmergencyNumber to compare
+     * @param ignoreServiceCategoryAndUrns {@code true} Ignore comparing of service category
+     * and Urns so that they can be determined from most precedent number. {@code false} compare
+     * those fields for determing duplicate.
      * @return true if they are the same EmergencyNumbers; false otherwise.
      *
      * @hide
      */
     public static boolean areSameEmergencyNumbers(@NonNull EmergencyNumber first,
-                                                  @NonNull EmergencyNumber second) {
+            @NonNull EmergencyNumber second, boolean ignoreServiceCategoryAndUrns) {
         if (!first.getNumber().equals(second.getNumber())) {
             return false;
         }
@@ -653,12 +697,14 @@
         if (!first.getMnc().equals(second.getMnc())) {
             return false;
         }
-        if (first.getEmergencyServiceCategoryBitmask()
-                != second.getEmergencyServiceCategoryBitmask()) {
-            return false;
-        }
-        if (!first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
-            return false;
+        if (!ignoreServiceCategoryAndUrns) {
+            if (first.getEmergencyServiceCategoryBitmask()
+                    != second.getEmergencyServiceCategoryBitmask()) {
+                return false;
+            }
+            if (!first.getEmergencyUrns().equals(second.getEmergencyUrns())) {
+                return false;
+            }
         }
         // Never merge two numbers if one of them is from test mode but the other one is not;
         // This supports to remove a number from the test mode.
@@ -681,7 +727,7 @@
      */
     public static EmergencyNumber mergeSameEmergencyNumbers(@NonNull EmergencyNumber first,
                                                             @NonNull EmergencyNumber second) {
-        if (areSameEmergencyNumbers(first, second)) {
+        if (areSameEmergencyNumbers(first, second, false)) {
             int routing = first.getEmergencyCallRouting();
 
             if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
@@ -699,6 +745,115 @@
     }
 
     /**
+     * Get merged EmergencyUrns list from two same emergency numbers.
+     * By giving priority to the urns from first number.
+     *
+     * @param firstEmergencyUrns first number's Urns
+     * @param secondEmergencyUrns second number's Urns
+     * @return a merged Urns
+     *
+     * @hide
+     */
+    private static List<String> mergeEmergencyUrns(@NonNull List<String> firstEmergencyUrns,
+            @NonNull List<String> secondEmergencyUrns) {
+        List<String> mergedUrns = new ArrayList<String>();
+        mergedUrns.addAll(firstEmergencyUrns);
+        for (String urn : secondEmergencyUrns) {
+            if (!firstEmergencyUrns.contains(urn)) {
+                mergedUrns.add(urn);
+            }
+        }
+        return mergedUrns;
+    }
+
+    /**
+     * Get the highest precedence source of the given Emergency number. Then get service catergory
+     * and urns list fill in the respective map with key as source.
+     *
+     * @param num EmergencyNumber to get the source, service category & urns
+     * @param serviceCategoryArray Array to store the category of the given EmergencyNumber
+     * with key as highest precedence source
+     * @param urnsArray Array to store the list of Urns of the given EmergencyNumber
+     * with key as highest precedence source
+     *
+     * @hide
+     */
+    private static void fillServiceCategoryAndUrns(@NonNull EmergencyNumber num,
+            @NonNull SparseIntArray serviceCategoryArray,
+            @NonNull SparseArray<List<String>> urnsArray) {
+        int numberSrc = num.getEmergencyNumberSourceBitmask();
+        for (Integer source : EMERGENCY_NUMBER_SOURCE_PRECEDENCE) {
+            if ((numberSrc & source) == source) {
+                if (!num.isInEmergencyServiceCategories(EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED)) {
+                    serviceCategoryArray.put(source, num.getEmergencyServiceCategoryBitmask());
+                }
+                urnsArray.put(source, num.getEmergencyUrns());
+                break;
+            }
+        }
+    }
+
+    /**
+     * Get a merged EmergencyNumber from two same emergency numbers from
+     * Emergency number list. Two emergency numbers are the same if
+     * {@link #areSameEmergencyNumbers} returns {@code true}.
+     *
+     * @param first first EmergencyNumber to compare
+     * @param second second EmergencyNumber to compare
+     * @param mergeServiceCategoriesAndUrns {@code true} then determine service category and urns
+     * Service catetory : set from most precedence source number(N/W, SIM, DB, modem_cfg)
+     * Urns : merge from both with first priority from most precedence source number
+     * {@code false} then call {@link #mergeSameEmergencyNumbers} to merge.
+     * @return a merged EmergencyNumber or null if they are not the same EmergencyNumber
+     *
+     * @hide
+     */
+    public static @NonNull EmergencyNumber mergeSameEmergencyNumbers(
+            @NonNull EmergencyNumber first, @NonNull EmergencyNumber second,
+            boolean mergeServiceCategoriesAndUrns) {
+        if (!mergeServiceCategoriesAndUrns) {
+            return mergeSameEmergencyNumbers(first, second);
+        }
+
+        int routing = first.getEmergencyCallRouting();
+        int serviceCategory = first.getEmergencyServiceCategoryBitmask();
+        List<String> mergedEmergencyUrns = new ArrayList<String>();
+        //Maps to store the service category and urns of both the first and second emergency number
+        // with key as most precedent source
+        SparseIntArray serviceCategoryArray = new SparseIntArray(2);
+        SparseArray<List<String>> urnsArray = new SparseArray(2);
+
+        fillServiceCategoryAndUrns(first, serviceCategoryArray, urnsArray);
+        fillServiceCategoryAndUrns(second, serviceCategoryArray, urnsArray);
+
+        if (second.isFromSources(EMERGENCY_NUMBER_SOURCE_DATABASE)) {
+            routing = second.getEmergencyCallRouting();
+        }
+
+        // Determine serviceCategory of most precedence number
+        for (int sourceOfCategory : EMERGENCY_NUMBER_SOURCE_PRECEDENCE) {
+            if (serviceCategoryArray.indexOfKey(sourceOfCategory) >= 0) {
+                serviceCategory = serviceCategoryArray.get(sourceOfCategory);
+                break;
+            }
+        }
+
+        // Merge Urns in precedence number
+        for (int sourceOfUrn : EMERGENCY_NUMBER_SOURCE_PRECEDENCE) {
+            if (urnsArray.contains(sourceOfUrn)) {
+                mergedEmergencyUrns = mergeEmergencyUrns(mergedEmergencyUrns,
+                        urnsArray.get(sourceOfUrn));
+            }
+        }
+
+        return new EmergencyNumber(first.getNumber(), first.getCountryIso(), first.getMnc(),
+                serviceCategory, mergedEmergencyUrns,
+                first.getEmergencyNumberSourceBitmask()
+                        | second.getEmergencyNumberSourceBitmask(),
+                routing);
+    }
+
+    /**
      * Validate Emergency Number address that only contains the dialable character
      * {@link PhoneNumberUtils#isDialable(char)}
      *
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index de9bbb6..83893ba 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -18,6 +18,7 @@
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
+        "mockito-target-minus-junit4",
         "services.core.unboosted",
         "testables",
         "truth-prebuilt",
diff --git a/tests/Input/src/com/android/test/input/MotionPredictorTest.kt b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
new file mode 100644
index 0000000..46aad9f
--- /dev/null
+++ b/tests/Input/src/com/android/test/input/MotionPredictorTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.test.input
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.view.InputDevice
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.PointerCoords
+import android.view.MotionEvent.PointerProperties
+import android.view.MotionPredictor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.time.Duration
+
+private fun getStylusMotionEvent(
+        eventTime: Duration,
+        action: Int,
+        x: Float,
+        y: Float,
+        ): MotionEvent{
+    // One-time: send a DOWN event
+    val pointerCount = 1
+    val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
+    val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
+
+    for (i in 0 until pointerCount) {
+        properties[i] = PointerProperties()
+        properties[i]!!.id = i
+        properties[i]!!.toolType = MotionEvent.TOOL_TYPE_STYLUS
+        coords[i] = PointerCoords()
+        coords[i]!!.x = x
+        coords[i]!!.y = y
+    }
+
+    return MotionEvent.obtain(/*downTime=*/0, eventTime.toMillis(), action, properties.size,
+                properties, coords, /*metaState=*/0, /*buttonState=*/0,
+                /*xPrecision=*/0f, /*yPrecision=*/0f, /*deviceId=*/0, /*edgeFlags=*/0,
+                InputDevice.SOURCE_STYLUS, /*flags=*/0)
+}
+
+private fun getPredictionContext(offset: Duration, enablePrediction: Boolean): Context {
+    val context = mock(Context::class.java)
+    val resources: Resources = mock(Resources::class.java)
+    `when`(context.getResources()).thenReturn(resources)
+    `when`(resources.getInteger(
+            com.android.internal.R.integer.config_motionPredictionOffsetNanos)).thenReturn(
+                offset.toNanos().toInt())
+    `when`(resources.getBoolean(
+            com.android.internal.R.bool.config_enableMotionPrediction)).thenReturn(enablePrediction)
+    return context
+}
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MotionPredictorTest {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    val initialPropertyValue = SystemProperties.get("persist.input.enable_motion_prediction")
+
+    @Before
+    fun setUp() {
+        instrumentation.uiAutomation.executeShellCommand(
+            "setprop persist.input.enable_motion_prediction true")
+    }
+
+    @After
+    fun tearDown() {
+        instrumentation.uiAutomation.executeShellCommand(
+            "setprop persist.input.enable_motion_prediction $initialPropertyValue")
+    }
+
+    /**
+     * In a typical usage, app will send the event to the predictor and then call .predict to draw
+     * a prediction. Here, we send 2 events to the predictor and check the returned event.
+     * Input:
+     * t = 0 x = 0 y = 0
+     * t = 1 x = 1 y = 2
+     * Output (expected):
+     * t = 3 x = 3 y = 6
+     *
+     * Historical data is ignored for simplicity.
+     */
+    @Test
+    fun testPredictedCoordinatesAndTime() {
+        val context = getPredictionContext(
+            /*offset=*/Duration.ofMillis(1), /*enablePrediction=*/true)
+        val predictor = MotionPredictor(context)
+        var eventTime = Duration.ofMillis(0)
+        val downEvent = getStylusMotionEvent(eventTime, ACTION_DOWN, /*x=*/0f, /*y=*/0f)
+        // ACTION_DOWN t=0 x=0 y=0
+        predictor.record(downEvent)
+
+        eventTime += Duration.ofMillis(1)
+        val moveEvent = getStylusMotionEvent(eventTime, ACTION_MOVE, /*x=*/1f, /*y=*/2f)
+        // ACTION_MOVE t=1 x=1 y=2
+        predictor.record(moveEvent)
+
+        val predicted = predictor.predict(Duration.ofMillis(2).toNanos())
+        assertEquals(1, predicted.size)
+        val event = predicted[0]
+        assertNotNull(event)
+
+        // Prediction will happen for t=3 (2 + 1, since offset is 1 and present time is 2)
+        assertEquals(3, event.eventTime)
+        assertEquals(3f, event.x, /*delta=*/0.001f)
+        assertEquals(6f, event.y, /*delta=*/0.001f)
+    }
+}
diff --git a/tests/MotionPrediction/Android.bp b/tests/MotionPrediction/Android.bp
new file mode 100644
index 0000000..b9d01da
--- /dev/null
+++ b/tests/MotionPrediction/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2023 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+    name: "MotionPrediction",
+    srcs: ["**/*.kt"],
+    platform_apis: true,
+    certificate: "platform",
+}
diff --git a/tests/MotionPrediction/AndroidManifest.xml b/tests/MotionPrediction/AndroidManifest.xml
new file mode 100644
index 0000000..3f8c2f2
--- /dev/null
+++ b/tests/MotionPrediction/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="test.motionprediction">
+
+    <application android:allowBackup="false"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/tests/MotionPrediction/OWNERS b/tests/MotionPrediction/OWNERS
new file mode 100644
index 0000000..c88bfe9
--- /dev/null
+++ b/tests/MotionPrediction/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/tests/MotionPrediction/res/layout/activity_main.xml b/tests/MotionPrediction/res/layout/activity_main.xml
new file mode 100644
index 0000000..65dc325
--- /dev/null
+++ b/tests/MotionPrediction/res/layout/activity_main.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context="test.motionprediction.MainActivity">
+
+    <test.motionprediction.DrawingView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@+id/output" />
+
+</LinearLayout>
diff --git a/tests/MotionPrediction/res/mipmap-hdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/MotionPrediction/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MotionPrediction/res/mipmap-mdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/MotionPrediction/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MotionPrediction/res/mipmap-xhdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/MotionPrediction/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MotionPrediction/res/mipmap-xxhdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/MotionPrediction/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MotionPrediction/res/mipmap-xxxhdpi/ic_launcher.png b/tests/MotionPrediction/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/tests/MotionPrediction/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/MotionPrediction/res/values-w820dp/dimens.xml b/tests/MotionPrediction/res/values-w820dp/dimens.xml
new file mode 100644
index 0000000..95669e6
--- /dev/null
+++ b/tests/MotionPrediction/res/values-w820dp/dimens.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2023 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.
+-->
+<resources>
+    <!-- Example customization of dimensions originally defined in res/values/dimens.xml
+         (such as screen margins) for screens with more than 820dp of available width. This
+         would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
+    <dimen name="activity_horizontal_margin">64dp</dimen>
+</resources>
diff --git a/tests/MotionPrediction/res/values/colors.xml b/tests/MotionPrediction/res/values/colors.xml
new file mode 100644
index 0000000..139eb1d
--- /dev/null
+++ b/tests/MotionPrediction/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
diff --git a/tests/MotionPrediction/res/values/dimens.xml b/tests/MotionPrediction/res/values/dimens.xml
new file mode 100644
index 0000000..d26136f
--- /dev/null
+++ b/tests/MotionPrediction/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<!-- Copyright (C) 2023 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.
+-->
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+</resources>
diff --git a/tests/MotionPrediction/res/values/strings.xml b/tests/MotionPrediction/res/values/strings.xml
new file mode 100644
index 0000000..16a2bdf
--- /dev/null
+++ b/tests/MotionPrediction/res/values/strings.xml
@@ -0,0 +1,17 @@
+<!-- Copyright (C) 2023 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.
+-->
+<resources>
+    <string name="app_name">Motion Prediction</string>
+</resources>
diff --git a/tests/MotionPrediction/res/values/styles.xml b/tests/MotionPrediction/res/values/styles.xml
new file mode 100644
index 0000000..cfb5e3d
--- /dev/null
+++ b/tests/MotionPrediction/res/values/styles.xml
@@ -0,0 +1,23 @@
+<!-- Copyright (C) 2023 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.
+-->
+<resources>
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="android:colorPrimary">@color/colorPrimary</item>
+        <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="android:colorAccent">@color/colorAccent</item>
+    </style>
+</resources>
diff --git a/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
new file mode 100644
index 0000000..f529bf7
--- /dev/null
+++ b/tests/MotionPrediction/src/test/motionprediction/DrawingView.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 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 test.motionprediction
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionPredictor
+import android.view.View
+
+import java.util.Vector
+
+private fun drawLine(canvas: Canvas, from: MotionEvent, to: MotionEvent, paint: Paint) {
+    canvas.apply {
+        val x0 = from.getX()
+        val y0 = from.getY()
+        val x1 = to.getX()
+        val y1 = to.getY()
+        // TODO: handle historical data
+        drawLine(x0, y0, x1, y1, paint)
+    }
+}
+
+/**
+ * Draw the current stroke and predicted values
+ */
+class DrawingView(context: Context, attrs: AttributeSet) : View(context, attrs) {
+    private val TAG = "DrawingView"
+
+    val events: MutableMap<Int, Vector<MotionEvent>> = mutableMapOf<Int, Vector<MotionEvent>>()
+
+    var isPredictionAvailable = false
+    private val predictor = MotionPredictor(getContext())
+
+    private var predictionPaint = Paint()
+    private var realPaint = Paint()
+
+    init {
+        setBackgroundColor(Color.WHITE)
+        predictionPaint.color = Color.BLACK
+        predictionPaint.setStrokeWidth(5f)
+        realPaint.color = Color.RED
+        realPaint.setStrokeWidth(5f)
+    }
+
+    private fun addEvent(event: MotionEvent) {
+        if (event.getActionMasked() == ACTION_DOWN) {
+            events.remove(event.deviceId)
+        }
+        var vec = events.getOrPut(event.deviceId) { Vector<MotionEvent>() }
+        vec.add(MotionEvent.obtain(event))
+        predictor.record(event)
+        invalidate()
+    }
+
+    public override fun onTouchEvent(event: MotionEvent): Boolean {
+        isPredictionAvailable = predictor.isPredictionAvailable(event.getDeviceId(),
+                                                                event.getSource())
+        addEvent(event)
+        return true
+    }
+
+    public override fun onDraw(canvas: Canvas) {
+        super.onDraw(canvas)
+        if (!isPredictionAvailable) {
+            canvas.apply {
+                drawRect(0f, 0f, 200f, 200f, realPaint)
+            }
+        }
+
+        var eventTime = 0L
+
+        // Draw real events
+        for ((_, vec) in events ) {
+            for (i in 1 until vec.size) {
+                drawLine(canvas, vec[i - 1], vec[i], realPaint)
+            }
+            eventTime = vec.lastElement().eventTime
+        }
+
+        // Draw predictions. Convert to nanos and hardcode to +20ms into the future
+        val predictionList = predictor.predict(eventTime * 1000000 + 20000000)
+        for (prediction in predictionList) {
+            val realEvents = events.get(prediction.deviceId)!!
+            drawLine(canvas, realEvents[realEvents.size - 1], prediction, predictionPaint)
+        }
+    }
+}
diff --git a/tests/MotionPrediction/src/test/motionprediction/MainActivity.kt b/tests/MotionPrediction/src/test/motionprediction/MainActivity.kt
new file mode 100644
index 0000000..cec2c06
--- /dev/null
+++ b/tests/MotionPrediction/src/test/motionprediction/MainActivity.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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 test.motionprediction
+
+import android.app.Activity
+import android.os.Bundle
+
+class MainActivity : Activity() {
+    val TAG = "MotionPrediction"
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_main)
+    }
+}