Snap for 7209848 from d5a19955eb9856372e5b0dd369bc4e6be5683f49 to tm-release

Change-Id: Id26d01534c529e7b8b9ab47d8ba6f686f82ce733
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index 5ded446..386c6e2 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -18,9 +18,10 @@
   }
 
   public static final class AppSearchManager.SearchContext.Builder {
-    ctor public AppSearchManager.SearchContext.Builder();
+    ctor @Deprecated public AppSearchManager.SearchContext.Builder();
+    ctor public AppSearchManager.SearchContext.Builder(@NonNull String);
     method @NonNull public android.app.appsearch.AppSearchManager.SearchContext build();
-    method @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
+    method @Deprecated @NonNull public android.app.appsearch.AppSearchManager.SearchContext.Builder setDatabaseName(@NonNull String);
   }
 
   public interface AppSearchMigrationHelper {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index a62bb50..0c6b86b 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -30,17 +30,19 @@
  * Provides access to the centralized AppSearch index maintained by the system.
  *
  * <p>AppSearch is a search library for managing structured data featuring:
+ *
  * <ul>
- *     <li>A fully offline on-device solution
- *     <li>A set of APIs for applications to index documents and retrieve them via full-text search
- *     <li>APIs for applications to allow the System to display their content on system UI surfaces
- *     <li>Similarly, APIs for applications to allow the System to share their content with other
- *     specified applications.
+ *   <li>A fully offline on-device solution
+ *   <li>A set of APIs for applications to index documents and retrieve them via full-text search
+ *   <li>APIs for applications to allow the System to display their content on system UI surfaces
+ *   <li>Similarly, APIs for applications to allow the System to share their content with other
+ *       specified applications.
  * </ul>
  *
  * <p>Applications create a database by opening an {@link AppSearchSession}.
  *
  * <p>Example:
+ *
  * <pre>
  * AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
  *
@@ -51,11 +53,12 @@
  * });</pre>
  *
  * <p>After opening the session, a schema must be set in order to define the organizational
- * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema
- * is composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique
- * type of data.
+ * structure of data. The schema is set by calling {@link AppSearchSession#setSchema}. The schema is
+ * composed of a collection of {@link AppSearchSchema} objects, each of which defines a unique type
+ * of data.
  *
  * <p>Example:
+ *
  * <pre>
  * AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email")
  *     .addProperty(new StringPropertyConfig.Builder("subject")
@@ -73,15 +76,16 @@
  * });</pre>
  *
  * <p>The basic unit of data in AppSearch is represented as a {@link GenericDocument} object,
- * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a
- * logical group of documents. For example, a namespace can be created to group documents on a
- * per-account basis. A URI identifies a single document within a namespace. The combination
- * of URI and namespace uniquely identifies a {@link GenericDocument} in the database.
+ * containing a URI, namespace, time-to-live, score, and properties. A namespace organizes a logical
+ * group of documents. For example, a namespace can be created to group documents on a per-account
+ * basis. A URI identifies a single document within a namespace. The combination of URI and
+ * namespace uniquely identifies a {@link GenericDocument} in the database.
  *
- * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database
- * and indexed by calling {@link AppSearchSession#put}.
+ * <p>Once the schema has been set, {@link GenericDocument} objects can be put into the database and
+ * indexed by calling {@link AppSearchSession#put}.
  *
  * <p>Example:
+ *
  * <pre>
  * // Although for this example we use GenericDocument directly, we recommend extending
  * // GenericDocument to create specific types (i.e. Email) with specific setters/getters.
@@ -106,18 +110,12 @@
  * and namespace.
  *
  * <p>Document removal is done either by time-to-live expiration, or explicitly calling a remove
- * operation. Remove operations can be done by URI and namespace via
- * {@link AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)},
- * or by query via {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
+ * operation. Remove operations can be done by URI and namespace via {@link
+ * AppSearchSession#remove(RemoveByUriRequest, Executor, BatchResultCallback)}, or by query via
+ * {@link AppSearchSession#remove(String, SearchSpec, Executor, Consumer)}.
  */
 @SystemService(Context.APP_SEARCH_SERVICE)
 public class AppSearchManager {
-    /**
-     * The default empty database name.
-     *
-     * @hide
-     */
-    public static final String DEFAULT_DATABASE_NAME = "";
 
     private final IAppSearchManager mService;
     private final Context mContext;
@@ -149,10 +147,42 @@
 
         /** Builder for {@link SearchContext} objects. */
         public static final class Builder {
-            private String mDatabaseName = DEFAULT_DATABASE_NAME;
+            private String mDatabaseName;
             private boolean mBuilt = false;
 
             /**
+             * TODO(b/181887768): This method exists only for dogfooder transition and must be
+             * removed.
+             *
+             * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
+             *     method exists only for dogfooder transition and must be removed.
+             */
+            @Deprecated
+            public Builder() {
+                mDatabaseName = "";
+            }
+
+            /**
+             * Creates a new {@link SearchContext.Builder}.
+             *
+             * <p>{@link AppSearchSession} will create or open a database under the given name.
+             *
+             * <p>Databases with different names are fully separate with distinct types, namespaces,
+             * and data.
+             *
+             * <p>Database name cannot contain {@code '/'}.
+             *
+             * @param databaseName The name of the database.
+             * @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+             */
+            public Builder(@NonNull String databaseName) {
+                Objects.requireNonNull(databaseName);
+                Preconditions.checkArgument(
+                        !databaseName.contains("/"), "Database name cannot contain '/'");
+                mDatabaseName = databaseName;
+            }
+
+            /**
              * Sets the name of the database associated with {@link AppSearchSession}.
              *
              * <p>{@link AppSearchSession} will create or open a database under the given name.
@@ -164,16 +194,21 @@
              *
              * <p>If not specified, defaults to the empty string.
              *
+             * <p>TODO(b/181887768): This method exists only for dogfooder transition and must be
+             * removed.
+             *
              * @param databaseName The name of the database.
              * @throws IllegalArgumentException if the databaseName contains {@code '/'}.
+             * @deprecated Please supply the databaseName in {@link #Builder(String)} instead. This
+             *     method exists only for dogfooder transition and must be removed.
              */
+            @Deprecated
             @NonNull
             public Builder setDatabaseName(@NonNull String databaseName) {
                 Preconditions.checkState(!mBuilt, "Builder has already been used");
                 Objects.requireNonNull(databaseName);
-                if (databaseName.contains("/")) {
-                    throw new IllegalArgumentException("Database name cannot contain '/'");
-                }
+                Preconditions.checkArgument(
+                        !databaseName.contains("/"), "Database name cannot contain '/'");
                 mDatabaseName = databaseName;
                 return this;
             }
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index f379739..486acb4 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -241,6 +241,7 @@
             documentBundles.add(documents.get(i).getBundle());
         }
         try {
+            // TODO(b/173532925) a timestamp needs to be sent here to calculate binder latency
             mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
                     new IAppSearchBatchResultCallback.Stub() {
                         public void onResult(AppSearchBatchResult result) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
new file mode 100644
index 0000000..aeb66d9
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2021 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.appsearch.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.appsearch.external.localstorage.AppSearchLogger;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
+
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * Logger Implementation using Westworld.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @hide
+ */
+public final class PlatformLogger implements AppSearchLogger {
+    private static final String TAG = "AppSearchPlatformLogger";
+
+    // Context of the system service.
+    private final Context mContext;
+
+    // User ID of the caller who we're logging for.
+    private final int mUserId;
+
+    // Configuration for the logger
+    private final Config mConfig;
+
+    private final Random mRng = new Random();
+    private final Object mLock = new Object();
+
+    /**
+     * SparseArray to track how many stats we skipped due to
+     * {@link Config#mMinTimeIntervalBetweenSamplesMillis}.
+     *
+     * <p> We can have correct extrapolated number by adding those counts back when we log
+     * the same type of stats next time. E.g. the true count of an event could be estimated as:
+     * SUM(sampling_ratio * (num_skipped_sample + 1)) as est_count
+     *
+     * <p>The key to the SparseArray is {@link CallStats.CallType}
+     */
+    @GuardedBy("mLock")
+    private final SparseIntArray mSkippedSampleCountLocked =
+            new SparseIntArray();
+
+    /**
+     * Map to cache the packageUid for each package.
+     *
+     * <p>It maps packageName to packageUid.
+     *
+     * <p>The entry will be removed whenever the app gets uninstalled
+     */
+    @GuardedBy("mLock")
+    private final Map<String, Integer> mPackageUidCacheLocked =
+            new ArrayMap<>();
+
+    /**
+     * Elapsed time for last stats logged from boot in millis
+     */
+    @GuardedBy("mLock")
+    private long mLastPushTimeMillisLocked = 0;
+
+    /**
+     * Class to configure the {@link PlatformLogger}
+     */
+    public static final class Config {
+        // Minimum time interval (in millis) since last message logged to Westworld before
+        // logging again.
+        private final long mMinTimeIntervalBetweenSamplesMillis;
+
+        // Default sampling ratio for all types of stats
+        private final int mDefaultSamplingRatio;
+
+        /**
+         * Sampling ratios for different types of stats
+         *
+         * <p>This SparseArray is passed by client and is READ-ONLY. The key to that SparseArray is
+         * {@link CallStats.CallType}
+         *
+         * <p>If sampling ratio is missing for certain stats type,
+         * {@link Config#mDefaultSamplingRatio} will be used.
+         *
+         * <p>E.g. sampling ratio=10 means that one out of every 10 stats was logged. If sampling
+         * ratio is 1, we will log each sample and it acts as if the sampling is disabled.
+         */
+        @NonNull
+        private final SparseIntArray mSamplingRatios;
+
+        /**
+         * Configuration for {@link PlatformLogger}
+         *
+         * @param minTimeIntervalBetweenSamplesMillis minimum time interval apart in Milliseconds
+         *                                            required for two consecutive stats logged
+         * @param defaultSamplingRatio                default sampling ratio
+         * @param samplingRatios                    SparseArray to customize sampling ratio for
+         *                                            different stat types
+         */
+        public Config(long minTimeIntervalBetweenSamplesMillis,
+                int defaultSamplingRatio,
+                @Nullable SparseIntArray samplingRatios) {
+            mMinTimeIntervalBetweenSamplesMillis = minTimeIntervalBetweenSamplesMillis;
+            mDefaultSamplingRatio = defaultSamplingRatio;
+            if (samplingRatios != null) {
+                mSamplingRatios = samplingRatios;
+            } else {
+                mSamplingRatios = new SparseIntArray();
+            }
+        }
+    }
+
+    /**
+     * Helper class to hold platform specific stats for Westworld.
+     */
+    static final class ExtraStats {
+        // UID for the calling package of the stats.
+        final int mPackageUid;
+        // sampling ratio for the call type of the stats.
+        final int mSamplingRatio;
+        // number of samplings skipped before the current one for the same call type.
+        final int mSkippedSampleCount;
+
+        ExtraStats(int packageUid, int samplingRatio, int skippedSampleCount) {
+            mPackageUid = packageUid;
+            mSamplingRatio = samplingRatio;
+            mSkippedSampleCount = skippedSampleCount;
+        }
+    }
+
+    /**
+     * Westworld constructor
+     */
+    public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) {
+        mContext = Preconditions.checkNotNull(context);
+        mConfig = Preconditions.checkNotNull(config);
+        mUserId = userId;
+    }
+
+    /** Logs {@link CallStats}. */
+    @Override
+    public void logStats(@NonNull CallStats stats) {
+        Preconditions.checkNotNull(stats);
+        synchronized (mLock) {
+            if (shouldLogForTypeLocked(stats.getCallType())) {
+                logToWestworldLocked(stats);
+            }
+        }
+    }
+
+    /** Logs {@link PutDocumentStats}. */
+    @Override
+    public void logStats(@NonNull PutDocumentStats stats) {
+        Preconditions.checkNotNull(stats);
+        synchronized (mLock) {
+            if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
+                logToWestworldLocked(stats);
+            }
+        }
+    }
+
+    /**
+     * Removes cached UID for package.
+     *
+     * @return removed UID for the package, or {@code INVALID_UID} if package was not previously
+     * cached.
+    */
+    public int removeCachedUidForPackage(@NonNull String packageName) {
+        // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
+        Preconditions.checkNotNull(packageName);
+        synchronized (mLock) {
+            Integer uid = mPackageUidCacheLocked.remove(packageName);
+            return uid != null ? uid : Process.INVALID_UID;
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void logToWestworldLocked(@NonNull CallStats stats) {
+        mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
+        ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
+                stats.getCallType());
+        /* TODO(b/173532925) Log the CallStats to Westworld
+        stats.log(..., samplingRatio, skippedSampleCount, ...)
+         */
+    }
+
+    @GuardedBy("mLock")
+    private void logToWestworldLocked(@NonNull PutDocumentStats stats) {
+        mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
+        ExtraStats extraStats = createExtraStatsLocked(stats.getGeneralStats().getPackageName(),
+                CallStats.CALL_TYPE_PUT_DOCUMENT);
+        /* TODO(b/173532925) Log the PutDocumentStats to Westworld
+        stats.log(..., samplingRatio, skippedSampleCount, ...)
+         */
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @NonNull
+    ExtraStats createExtraStatsLocked(@NonNull String packageName,
+            @CallStats.CallType int callType) {
+        int packageUid = getPackageUidAsUserLocked(packageName);
+        int samplingRatio = mConfig.mSamplingRatios.get(callType,
+                mConfig.mDefaultSamplingRatio);
+
+        int skippedSampleCount = mSkippedSampleCountLocked.get(callType,
+                /*valueOfKeyIfNotFound=*/ 0);
+        mSkippedSampleCountLocked.put(callType, 0);
+
+        return new ExtraStats(packageUid, samplingRatio, skippedSampleCount);
+    }
+
+    /**
+     * Checks if this stats should be logged.
+     *
+     * <p>It won't be logged if it is "sampled" out, or it is too close to the previous logged
+     * stats.
+     */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    boolean shouldLogForTypeLocked(@CallStats.CallType int callType) {
+        int samplingRatio = mConfig.mSamplingRatios.get(callType,
+                mConfig.mDefaultSamplingRatio);
+
+        // Sampling
+        if (!shouldSample(samplingRatio)) {
+            return false;
+        }
+
+        // Rate limiting
+        // Check the timestamp to see if it is too close to last logged sample
+        long currentTimeMillis = SystemClock.elapsedRealtime();
+        if (mLastPushTimeMillisLocked
+                > currentTimeMillis - mConfig.mMinTimeIntervalBetweenSamplesMillis) {
+            int count = mSkippedSampleCountLocked.get(callType, /*valueOfKeyIfNotFound=*/ 0);
+            ++count;
+            mSkippedSampleCountLocked.put(callType, count);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Checks if the stats should be "sampled"
+     *
+     * @param samplingRatio sampling ratio
+     * @return if the stats should be sampled
+     */
+    private boolean shouldSample(int samplingRatio) {
+        if (samplingRatio <= 0) {
+            return false;
+        }
+
+        return mRng.nextInt((int) samplingRatio) == 0;
+    }
+
+    /**
+     * Finds the UID of the {@code packageName}. Returns {@link Process#INVALID_UID} if unable to
+     * find the UID.
+     */
+    @GuardedBy("mLock")
+    private int getPackageUidAsUserLocked(@NonNull String packageName) {
+        Integer packageUid = mPackageUidCacheLocked.get(packageName);
+        if (packageUid != null) {
+            return packageUid;
+        }
+
+        // TODO(b/173532925) since VisibilityStore has the same method, we can make this a
+        //  utility function
+        try {
+            packageUid = mContext.getPackageManager().getPackageUidAsUser(packageName, mUserId);
+            mPackageUidCacheLocked.put(packageName, packageUid);
+            return packageUid;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Package doesn't exist, continue
+        }
+        return Process.INVALID_UID;
+    }
+
+    //
+    // Functions below are used for tests only
+    //
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    void setLastPushTimeMillisLocked(long lastPushElapsedTimeMillis) {
+        mLastPushTimeMillisLocked = lastPushElapsedTimeMillis;
+    }
+}
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 260c8a4..f5bee6c 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -258,7 +258,7 @@
 
     public void runDisableAppDataIsolation() throws RemoteException {
         if (!SystemProperties.getBoolean(
-                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
+                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
             throw new IllegalStateException("Storage app data isolation is not enabled.");
         }
         final String pkgName = nextArg();
diff --git a/core/api/current.txt b/core/api/current.txt
index 1acd0fa..6735268 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -3027,6 +3027,7 @@
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12; // 0xc
     field public static final int GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT = 13; // 0xd
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
+    field public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15; // 0xf
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
     field public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10; // 0xa
     field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
@@ -9909,6 +9910,7 @@
     method public String getHtmlText();
     method public android.content.Intent getIntent();
     method public CharSequence getText();
+    method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
     method public android.net.Uri getUri();
   }
 
@@ -9918,6 +9920,8 @@
     method public static boolean compareMimeTypes(String, String);
     method public int describeContents();
     method public String[] filterMimeTypes(String);
+    method public int getClassificationStatus();
+    method @FloatRange(from=0.0, to=1.0) public float getConfidenceScore(@NonNull String);
     method public android.os.PersistableBundle getExtras();
     method public CharSequence getLabel();
     method public String getMimeType(int);
@@ -9927,6 +9931,9 @@
     method public boolean isStyledText();
     method public void setExtras(android.os.PersistableBundle);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CLASSIFICATION_COMPLETE = 3; // 0x3
+    field public static final int CLASSIFICATION_NOT_COMPLETE = 1; // 0x1
+    field public static final int CLASSIFICATION_NOT_PERFORMED = 2; // 0x2
     field @NonNull public static final android.os.Parcelable.Creator<android.content.ClipDescription> CREATOR;
     field public static final String MIMETYPE_TEXT_HTML = "text/html";
     field public static final String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
@@ -10954,6 +10961,7 @@
     field public static final String ACTION_MANAGED_PROFILE_UNLOCKED = "android.intent.action.MANAGED_PROFILE_UNLOCKED";
     field public static final String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
     field public static final String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
+    field public static final String ACTION_MANAGE_UNUSED_APPS = "android.intent.action.MANAGE_UNUSED_APPS";
     field public static final String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
     field public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
     field public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING";
@@ -21889,6 +21897,7 @@
     method @NonNull public java.util.List<byte[]> getOfflineLicenseKeySetIds();
     method public int getOfflineLicenseState(@NonNull byte[]);
     method public int getOpenSessionCount();
+    method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
     method @NonNull public byte[] getPropertyByteArray(String);
     method @NonNull public String getPropertyString(@NonNull String);
     method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest();
@@ -36863,11 +36872,12 @@
     method @NonNull public static android.content.Intent createInstallIntent();
     method @NonNull public static android.content.Intent createManageCredentialsIntent(@NonNull android.security.AppUriAuthenticationPolicy);
     method @Nullable @WorkerThread public static java.security.cert.X509Certificate[] getCertificateChain(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException;
-    method @NonNull public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException;
+    method @NonNull @WorkerThread public static android.security.AppUriAuthenticationPolicy getCredentialManagementAppPolicy(@NonNull android.content.Context) throws java.lang.SecurityException;
     method @Nullable @WorkerThread public static java.security.PrivateKey getPrivateKey(@NonNull android.content.Context, @NonNull String) throws java.lang.InterruptedException, android.security.KeyChainException;
     method @Deprecated public static boolean isBoundKeyAlgorithm(@NonNull String);
-    method public static boolean isCredentialManagementApp(@NonNull android.content.Context);
+    method @WorkerThread public static boolean isCredentialManagementApp(@NonNull android.content.Context);
     method public static boolean isKeyAlgorithmSupported(@NonNull String);
+    method @RequiresPermission(value="android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP", conditional=true) @WorkerThread public static boolean removeCredentialManagementApp(@NonNull android.content.Context);
     field public static final String ACTION_KEYCHAIN_CHANGED = "android.security.action.KEYCHAIN_CHANGED";
     field public static final String ACTION_KEY_ACCESS_CHANGED = "android.security.action.KEY_ACCESS_CHANGED";
     field @Deprecated public static final String ACTION_STORAGE_CHANGED = "android.security.STORAGE_CHANGED";
@@ -51913,6 +51923,7 @@
     field public static final String TYPE_PHONE = "phone";
     field public static final String TYPE_UNKNOWN = "";
     field public static final String TYPE_URL = "url";
+    field public static final String WIDGET_TYPE_CLIPBOARD = "clipboard";
     field public static final String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit";
     field public static final String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview";
     field public static final String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
@@ -55027,6 +55038,7 @@
     method public void setRelativeScrollPosition(@IdRes int, int);
     method @Deprecated public void setRemoteAdapter(int, @IdRes int, android.content.Intent);
     method public void setRemoteAdapter(@IdRes int, android.content.Intent);
+    method public void setRemoteAdapter(@IdRes int, @NonNull android.widget.RemoteViews.RemoteCollectionItems);
     method public void setScrollPosition(@IdRes int, int);
     method public void setShort(@IdRes int, String, short);
     method public void setString(@IdRes int, String, String);
@@ -55066,6 +55078,25 @@
     ctor public RemoteViews.ActionException(String);
   }
 
+  public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getItemCount();
+    method public long getItemId(int);
+    method @NonNull public android.widget.RemoteViews getItemView(int);
+    method public int getViewTypeCount();
+    method public boolean hasStableIds();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews.RemoteCollectionItems> CREATOR;
+  }
+
+  public static final class RemoteViews.RemoteCollectionItems.Builder {
+    ctor public RemoteViews.RemoteCollectionItems.Builder();
+    method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder addItem(long, @NonNull android.widget.RemoteViews);
+    method @NonNull public android.widget.RemoteViews.RemoteCollectionItems build();
+    method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setHasStableIds(boolean);
+    method @NonNull public android.widget.RemoteViews.RemoteCollectionItems.Builder setViewTypeCount(int);
+  }
+
   public static class RemoteViews.RemoteResponse {
     ctor public RemoteViews.RemoteResponse();
     method @NonNull public android.widget.RemoteViews.RemoteResponse addSharedElement(@IdRes int, @NonNull String);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 18b0a43..a140e8a 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -55,6 +55,10 @@
 
 package android.content {
 
+  public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
+    method @NonNull public static android.net.Uri createContentUriAsUser(@NonNull android.net.Uri, @NonNull android.os.UserHandle);
+  }
+
   public abstract class Context {
     method @NonNull public android.os.UserHandle getUser();
   }
@@ -203,6 +207,16 @@
     method @Nullable public byte[] getWatchlistConfigHash();
   }
 
+  public class PacProxyManager {
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void addPacProxyInstalledListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.PacProxyManager.PacProxyInstalledListener);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void removePacProxyInstalledListener(@NonNull android.net.PacProxyManager.PacProxyInstalledListener);
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setCurrentProxyScriptUrl(@Nullable android.net.ProxyInfo);
+  }
+
+  public static interface PacProxyManager.PacProxyInstalledListener {
+    method public void onPacProxyInstalled(@Nullable android.net.Network, @NonNull android.net.ProxyInfo);
+  }
+
   public final class Proxy {
     method public static void setHttpProxyConfiguration(@Nullable android.net.ProxyInfo);
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 8744ce1..560dded 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1888,6 +1888,7 @@
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
     method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
@@ -2064,6 +2065,54 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BufferConstraints> CREATOR;
   }
 
+  public final class OobData implements android.os.Parcelable {
+    method @NonNull public static android.bluetooth.OobData.ClassicBuilder createClassicBuilder(@NonNull byte[], @NonNull byte[], @NonNull byte[]);
+    method @NonNull public static android.bluetooth.OobData.LeBuilder createLeBuilder(@NonNull byte[], @NonNull byte[], int);
+    method @NonNull public byte[] getClassOfDevice();
+    method @NonNull public byte[] getClassicLength();
+    method @NonNull public byte[] getConfirmationHash();
+    method @NonNull public byte[] getDeviceAddressWithType();
+    method @Nullable public byte[] getDeviceName();
+    method @Nullable public byte[] getLeAppearance();
+    method @NonNull public int getLeDeviceRole();
+    method @NonNull public int getLeFlags();
+    method @Nullable public byte[] getLeTemporaryKey();
+    method @NonNull public byte[] getRandomizerHash();
+    field public static final int CLASS_OF_DEVICE_OCTETS = 3; // 0x3
+    field public static final int CONFIRMATION_OCTETS = 16; // 0x10
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.OobData> CREATOR;
+    field public static final int DEVICE_ADDRESS_OCTETS = 7; // 0x7
+    field public static final int LE_APPEARANCE_OCTETS = 2; // 0x2
+    field public static final int LE_DEVICE_FLAG_OCTETS = 1; // 0x1
+    field public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 3; // 0x3
+    field public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 2; // 0x2
+    field public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 1; // 0x1
+    field public static final int LE_DEVICE_ROLE_OCTETS = 1; // 0x1
+    field public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0; // 0x0
+    field public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 2; // 0x2
+    field public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 1; // 0x1
+    field public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0; // 0x0
+    field public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 3; // 0x3
+    field public static final int LE_FLAG_SIMULTANEOUS_HOST = 4; // 0x4
+    field public static final int LE_TK_OCTETS = 16; // 0x10
+    field public static final int RANDOMIZER_OCTETS = 16; // 0x10
+  }
+
+  public static final class OobData.ClassicBuilder {
+    method @NonNull public android.bluetooth.OobData build();
+    method @NonNull public android.bluetooth.OobData.ClassicBuilder setClassOfDevice(@NonNull byte[]);
+    method @NonNull public android.bluetooth.OobData.ClassicBuilder setDeviceName(@NonNull byte[]);
+    method @NonNull public android.bluetooth.OobData.ClassicBuilder setRandomizerHash(@NonNull byte[]);
+  }
+
+  public static final class OobData.LeBuilder {
+    method @NonNull public android.bluetooth.OobData build();
+    method @NonNull public android.bluetooth.OobData.LeBuilder setDeviceName(@NonNull byte[]);
+    method @NonNull public android.bluetooth.OobData.LeBuilder setLeFlags(int);
+    method @NonNull public android.bluetooth.OobData.LeBuilder setLeTemporaryKey(@NonNull byte[]);
+    method @NonNull public android.bluetooth.OobData.LeBuilder setRandomizerHash(@NonNull byte[]);
+  }
+
 }
 
 package android.bluetooth.le {
@@ -5145,6 +5194,21 @@
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
   }
 
+  public final class MediaRouter2 {
+    method @NonNull public java.util.List<android.media.MediaRoute2Info> getAllRoutes();
+    method @Nullable public String getClientPackageName();
+    method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
+    method @Nullable public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context, @NonNull String);
+    method public void setRouteVolume(@NonNull android.media.MediaRoute2Info, int);
+    method public void startScan();
+    method public void stopScan();
+    method public void transfer(@NonNull android.media.MediaRouter2.RoutingController, @NonNull android.media.MediaRoute2Info);
+  }
+
+  public abstract static class MediaRouter2.RouteCallback {
+    method public void onPreferredFeaturesChanged(@NonNull java.util.List<java.lang.String>);
+  }
+
   public class PlayerProxy {
     method public void pause();
     method public void setPan(float);
@@ -9630,10 +9694,28 @@
 
 package android.service.displayhash {
 
+  public final class DisplayHashParams implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.util.Size getBufferSize();
+    method public boolean isBufferScaleWithFiltering();
+    method public boolean isGrayscaleBuffer();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.displayhash.DisplayHashParams> CREATOR;
+  }
+
+  public static final class DisplayHashParams.Builder {
+    ctor public DisplayHashParams.Builder();
+    method @NonNull public android.service.displayhash.DisplayHashParams build();
+    method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean);
+    method @NonNull public android.service.displayhash.DisplayHashParams.Builder setBufferSize(int, int);
+    method @NonNull public android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean);
+  }
+
   public abstract class DisplayHasherService extends android.app.Service {
     ctor public DisplayHasherService();
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @Nullable public abstract void onGenerateDisplayHash(@NonNull byte[], @NonNull android.hardware.HardwareBuffer, @NonNull android.graphics.Rect, @NonNull String, @NonNull android.view.displayhash.DisplayHashResultCallback);
+    method @NonNull public abstract java.util.Map<java.lang.String,android.service.displayhash.DisplayHashParams> onGetDisplayHashAlgorithms();
     method @Nullable public abstract android.view.displayhash.VerifiedDisplayHash onVerifyDisplayHash(@NonNull byte[], @NonNull android.view.displayhash.DisplayHash);
     field public static final String SERVICE_INTERFACE = "android.service.displayhash.DisplayHasherService";
   }
@@ -11616,7 +11698,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isLteCdmaEvdoGsmWcdmaEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isMobileDataPolicyEnabled(int);
-    method public boolean isNrDualConnectivityEnabled();
+    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isNrDualConnectivityEnabled();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
@@ -11702,6 +11784,7 @@
     field public static final int CALL_WAITING_STATUS_NOT_SUPPORTED = 4; // 0x4
     field public static final int CALL_WAITING_STATUS_UNKNOWN_ERROR = 3; // 0x3
     field public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED = "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
+    field public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE = "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
     field public static final String CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE = "CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE";
     field public static final int CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES = -2; // 0xfffffffe
     field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9fde791..1587475 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1380,10 +1380,6 @@
     method public int getMaxMacroBlocks();
   }
 
-  public final class MediaDrm implements java.lang.AutoCloseable {
-    method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
-  }
-
   public final class MediaRoute2Info implements android.os.Parcelable {
     method @NonNull public String getOriginalId();
   }
@@ -1950,8 +1946,7 @@
 package android.security {
 
   public final class KeyChain {
-    method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean removeCredentialManagementApp(@NonNull android.content.Context);
-    method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") public static boolean setCredentialManagementApp(@NonNull android.content.Context, @NonNull String, @NonNull android.security.AppUriAuthenticationPolicy);
+    method @RequiresPermission("android.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP") @WorkerThread public static boolean setCredentialManagementApp(@NonNull android.content.Context, @NonNull String, @NonNull android.security.AppUriAuthenticationPolicy);
   }
 
   public class KeyStoreException extends java.lang.Exception {
@@ -2736,6 +2731,7 @@
 
   public final class InputMethodManager {
     method public int getDisplayId();
+    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method public boolean isInputMethodPickerShown();
   }
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index dab4a5d..d536821 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -555,6 +555,11 @@
      */
     public static final int GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS = 14;
 
+    /**
+     * Action to dismiss the notification shade
+     */
+    public static final int GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE = 15;
+
     private static final String LOG_TAG = "AccessibilityService";
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3af0763..0358fe56 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2586,9 +2586,7 @@
     @Override
     public Context createTokenContext(@NonNull IBinder token, @NonNull Display display) {
         if (display == null) {
-            throw new UnsupportedOperationException("Token context can only be created from "
-                    + "other visual contexts, such as Activity or one created with "
-                    + "Context#createDisplayContext(Display)");
+            throw new IllegalArgumentException("Display must not be null");
         }
         final ContextImpl tokenContext = createBaseWindowContext(token, display);
         tokenContext.setResources(createWindowContextResources());
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 2b45723..a3a8a5e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5011,9 +5011,13 @@
             boolean showProgress = handleProgressBar(contentView, ex, p);
             boolean hasSecondLine = showProgress;
             if (p.hasTitle()) {
-                contentView.setViewVisibility(R.id.title, View.VISIBLE);
-                contentView.setTextViewText(R.id.title, processTextSpans(p.title));
-                setTextViewColorPrimary(contentView, R.id.title, p);
+                contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
+                contentView.setTextViewText(p.mTitleViewId, processTextSpans(p.title));
+                setTextViewColorPrimary(contentView, p.mTitleViewId, p);
+            } else if (p.mTitleViewId != R.id.title) {
+                // This alternate title view ID is not cleared by resetStandardTemplate
+                contentView.setViewVisibility(p.mTitleViewId, View.GONE);
+                contentView.setTextViewText(p.mTitleViewId, null);
             }
             if (p.text != null && p.text.length() != 0
                     && (!showProgress || p.mAllowTextWithProgress)) {
@@ -5307,7 +5311,7 @@
             contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
             contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
             // Use different highlighted colors except when low-priority mode prevents that
-            if (!p.forceDefaultColor) {
+            if (!p.mReduceHighlights) {
                 textColor = getBackgroundColor(p);
                 pillColor = getAccentColor(p);
             }
@@ -5441,6 +5445,11 @@
                 // keep the divider visible between that title and the next text element.
                 return true;
             }
+            if (p.mHideAppName) {
+                // The app name is being hidden, so we definitely want to return here.
+                // Assume that there is a title which will replace it in the header.
+                return p.hasTitle();
+            }
             contentView.setViewVisibility(R.id.app_name_text, View.VISIBLE);
             contentView.setTextViewText(R.id.app_name_text, loadHeaderAppName());
             contentView.setTextColor(R.id.app_name_text, getSecondaryTextColor(p));
@@ -5817,7 +5826,7 @@
          *
          * @hide
          */
-        public RemoteViews makeNotificationHeader() {
+        public RemoteViews makeNotificationGroupHeader() {
             return makeNotificationHeader(mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
                     .fillTextsFrom(this));
@@ -5943,7 +5952,7 @@
                     .viewType(StandardTemplateParams.VIEW_TYPE_PUBLIC)
                     .fillTextsFrom(this);
             if (isLowPriority) {
-                params.forceDefaultColor();
+                params.reduceHighlights();
             }
             view = makeNotificationHeader(params);
             view.setBoolean(R.id.notification_header, "setExpandOnlyOnButton", true);
@@ -5966,7 +5975,7 @@
         public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
             StandardTemplateParams p = mParams.reset()
                     .viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
-                    .forceDefaultColor()
+                    .reduceHighlights()
                     .fillTextsFrom(this);
             if (!useRegularSubtext || TextUtils.isEmpty(mParams.summaryText)) {
                 p.summaryText(createSummaryText());
@@ -6306,7 +6315,9 @@
          * @param p the template params to inflate this with
          */
         private @ColorInt int getRawColor(StandardTemplateParams p) {
-            if (p.forceDefaultColor) {
+            // When notifications are theme-tinted, the raw color is only used for the icon, so go
+            // ahead and keep that color instead of changing the color for minimized notifs.
+            if (p.mReduceHighlights && !mTintWithThemeAccent) {
                 return COLOR_DEFAULT;
             }
             return mN.color;
@@ -6583,10 +6594,6 @@
             return R.layout.notification_template_material_conversation;
         }
 
-        private int getCallLayoutResource() {
-            return R.layout.notification_template_material_call;
-        }
-
         private int getActionLayoutResource() {
             return R.layout.notification_material_action;
         }
@@ -9329,7 +9336,7 @@
          */
         @Override
         public RemoteViews makeContentView(boolean increasedHeight) {
-            return makeCallLayout();
+            return makeCallLayout(StandardTemplateParams.VIEW_TYPE_NORMAL);
         }
 
         /**
@@ -9337,14 +9344,14 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            return makeCallLayout();
+            return makeCallLayout(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
         }
 
         /**
          * @hide
          */
         public RemoteViews makeBigContentView() {
-            return makeCallLayout();
+            return makeCallLayout(StandardTemplateParams.VIEW_TYPE_BIG);
         }
 
         @NonNull
@@ -9443,8 +9450,10 @@
             return resultActions;
         }
 
-        private RemoteViews makeCallLayout() {
+        private RemoteViews makeCallLayout(int viewType) {
+            final boolean isCollapsed = viewType == StandardTemplateParams.VIEW_TYPE_NORMAL;
             Bundle extras = mBuilder.mN.extras;
+            CharSequence title = mPerson != null ? mPerson.getName() : null;
             CharSequence text = mBuilder.processLegacyText(extras.getCharSequence(EXTRA_TEXT));
             if (text == null) {
                 text = getDefaultText();
@@ -9452,20 +9461,30 @@
 
             // Bind standard template
             StandardTemplateParams p = mBuilder.mParams.reset()
-                    .viewType(StandardTemplateParams.VIEW_TYPE_BIG)
+                    .viewType(viewType)
                     .callStyleActions(true)
                     .allowTextWithProgress(true)
                     .hideLargeIcon(true)
+                    .hideAppName(isCollapsed)
+                    .titleViewId(R.id.conversation_text)
+                    .title(title)
                     .text(text)
                     .summaryText(mBuilder.processLegacyText(mVerificationText));
             mBuilder.mActions = getActionsListWithSystemActions();
-            RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
-                    mBuilder.getCallLayoutResource(), p, null /* result */);
+            final RemoteViews contentView;
+            if (isCollapsed) {
+                contentView = mBuilder.applyStandardTemplate(
+                        R.layout.notification_template_material_call, p, null /* result */);
+            } else {
+                contentView = mBuilder.applyStandardTemplateWithActions(
+                        R.layout.notification_template_material_big_call, p, null /* result */);
+            }
 
             // Bind some extra conversation-specific header fields.
-            mBuilder.setTextViewColorPrimary(contentView, R.id.conversation_text, p);
-            mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
-            contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+            if (!p.mHideAppName) {
+                mBuilder.setTextViewColorSecondary(contentView, R.id.app_name_divider, p);
+                contentView.setViewVisibility(R.id.app_name_divider, View.VISIBLE);
+            }
             bindCallerVerification(contentView, p);
 
             // Bind some custom CallLayout properties
@@ -12142,12 +12161,13 @@
         public static int VIEW_TYPE_NORMAL = 1;
         public static int VIEW_TYPE_BIG = 2;
         public static int VIEW_TYPE_HEADS_UP = 3;
-        public static int VIEW_TYPE_MINIMIZED = 4;
-        public static int VIEW_TYPE_PUBLIC = 5;
-        public static int VIEW_TYPE_GROUP_HEADER = 6;
+        public static int VIEW_TYPE_MINIMIZED = 4;    // header only for minimized state
+        public static int VIEW_TYPE_PUBLIC = 5;       // header only for automatic public version
+        public static int VIEW_TYPE_GROUP_HEADER = 6; // header only for top of group
 
         int mViewType = VIEW_TYPE_UNSPECIFIED;
         boolean mHeaderless;
+        boolean mHideAppName;
         boolean mHideTitle;
         boolean mHideActions;
         boolean mHideProgress;
@@ -12155,6 +12175,7 @@
         boolean mPromotePicture;
         boolean mCallStyleActions;
         boolean mAllowTextWithProgress;
+        int mTitleViewId;
         int mTextViewId;
         CharSequence title;
         CharSequence text;
@@ -12163,11 +12184,12 @@
         int maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
         boolean hideLargeIcon;
         boolean allowColorization  = true;
-        boolean forceDefaultColor = false;
+        boolean mReduceHighlights = false;
 
         final StandardTemplateParams reset() {
             mViewType = VIEW_TYPE_UNSPECIFIED;
             mHeaderless = false;
+            mHideAppName = false;
             mHideTitle = false;
             mHideActions = false;
             mHideProgress = false;
@@ -12175,6 +12197,7 @@
             mPromotePicture = false;
             mCallStyleActions = false;
             mAllowTextWithProgress = false;
+            mTitleViewId = R.id.title;
             mTextViewId = R.id.text;
             title = null;
             text = null;
@@ -12182,7 +12205,7 @@
             headerTextSecondary = null;
             maxRemoteInputHistory = Style.MAX_REMOTE_INPUT_HISTORY_LINES;
             allowColorization = true;
-            forceDefaultColor = false;
+            mReduceHighlights = false;
             return this;
         }
 
@@ -12200,6 +12223,11 @@
             return this;
         }
 
+        public StandardTemplateParams hideAppName(boolean hideAppName) {
+            mHideAppName = hideAppName;
+            return this;
+        }
+
         final StandardTemplateParams hideActions(boolean hideActions) {
             this.mHideActions = hideActions;
             return this;
@@ -12235,6 +12263,11 @@
             return this;
         }
 
+        public StandardTemplateParams titleViewId(int titleViewId) {
+            mTitleViewId = titleViewId;
+            return this;
+        }
+
         public StandardTemplateParams textViewId(int textViewId) {
             mTextViewId = textViewId;
             return this;
@@ -12270,8 +12303,8 @@
             return this;
         }
 
-        final StandardTemplateParams forceDefaultColor() {
-            this.forceDefaultColor = true;
+        final StandardTemplateParams reduceHighlights() {
+            this.mReduceHighlights = true;
             return this;
         }
 
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 43c14a9..31b0d41 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -129,11 +129,13 @@
 import android.net.IEthernetManager;
 import android.net.IIpSecService;
 import android.net.INetworkPolicyManager;
+import android.net.IPacProxyManager;
 import android.net.IVpnManager;
 import android.net.IpSecManager;
 import android.net.NetworkPolicyManager;
 import android.net.NetworkScoreManager;
 import android.net.NetworkWatchlistManager;
+import android.net.PacProxyManager;
 import android.net.TetheringManager;
 import android.net.VpnManager;
 import android.net.lowpan.ILowpanManager;
@@ -374,6 +376,15 @@
         // (which extends it).
         SYSTEM_SERVICE_NAMES.put(android.text.ClipboardManager.class, Context.CLIPBOARD_SERVICE);
 
+        registerService(Context.PAC_PROXY_SERVICE, PacProxyManager.class,
+                new CachedServiceFetcher<PacProxyManager>() {
+            @Override
+            public PacProxyManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                IBinder b = ServiceManager.getServiceOrThrow(Context.PAC_PROXY_SERVICE);
+                IPacProxyManager service = IPacProxyManager.Stub.asInterface(b);
+                return new PacProxyManager(ctx.getOuterContext(), service);
+            }});
+
         registerService(Context.NETD_SERVICE, IBinder.class, new StaticServiceFetcher<IBinder>() {
             @Override
             public IBinder createService() throws ServiceNotFoundException {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index ec94faa..07dbdce 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -1311,7 +1311,6 @@
      * the bonding process completes, and its result.
      * <p>Android system services will handle the necessary user interactions
      * to confirm and complete the bonding process.
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
      *
      * @param transport The transport to use for the pairing procedure.
      * @return false on immediate error, true if bonding will begin
@@ -1319,8 +1318,9 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
     public boolean createBond(int transport) {
-        return createBondOutOfBand(transport, null);
+        return createBondInternal(transport, null, null);
     }
 
     /**
@@ -1334,22 +1334,39 @@
      * <p>Android system services will handle the necessary user interactions
      * to confirm and complete the bonding process.
      *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+     * <p>There are two possible versions of OOB Data.  This data can come in as
+     * P192 or P256.  This is a reference to the cryptography used to generate the key.
+     * The caller may pass one or both.  If both types of data are passed, then the
+     * P256 data will be preferred, and thus used.
      *
      * @param transport - Transport to use
-     * @param oobData - Out Of Band data
+     * @param remoteP192Data - Out Of Band data (P192) or null
+     * @param remoteP256Data - Out Of Band data (P256) or null
      * @return false on immediate error, true if bonding will begin
      * @hide
      */
-    public boolean createBondOutOfBand(int transport, OobData oobData) {
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data,
+            @Nullable OobData remoteP256Data) {
+        if (remoteP192Data == null && remoteP256Data == null) {
+            throw new IllegalArgumentException(
+                "One or both arguments for the OOB data types are required to not be null."
+                + "  Please use createBond() instead if you do not have OOB data to pass.");
+        }
+        return createBondInternal(transport, remoteP192Data, remoteP256Data);
+    }
+
+    private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data,
+            @Nullable OobData remoteP256Data) {
         final IBluetooth service = sService;
         if (service == null) {
             Log.w(TAG, "BT not enabled, createBondOutOfBand failed");
             return false;
         }
         try {
-            BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-            return service.createBond(this, transport, oobData, adapter.getOpPackageName());
+            return service.createBond(this, transport, remoteP192Data, remoteP256Data,
+                    BluetoothAdapter.getDefaultAdapter().getOpPackageName());
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
         }
@@ -1380,27 +1397,6 @@
     }
 
     /**
-     * Set the Out Of Band data for a remote device to be used later
-     * in the pairing mechanism. Users can obtain this data through other
-     * trusted channels
-     *
-     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
-     *
-     * @param hash Simple Secure pairing hash
-     * @param randomizer The random key obtained using OOB
-     * @return false on error; true otherwise
-     * @hide
-     */
-    public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
-        //TODO(BT)
-      /*
-      try {
-        return sService.setDeviceOutOfBandData(this, hash, randomizer);
-      } catch (RemoteException e) {Log.e(TAG, "", e);} */
-        return false;
-    }
-
-    /**
      * Cancel an in-progress bonding request started with {@link #createBond}.
      *
      * @return true on success, false on error
diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java
index 0d0c6ab..9810746 100644
--- a/core/java/android/bluetooth/OobData.java
+++ b/core/java/android/bluetooth/OobData.java
@@ -1,4 +1,4 @@
-/*
+/**
  * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,88 +16,950 @@
 
 package android.bluetooth;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.Preconditions;
+
+import java.lang.IllegalArgumentException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Out Of Band Data for Bluetooth device pairing.
  *
  * <p>This object represents optional data obtained from a remote device through
- * an out-of-band channel (eg. NFC).
+ * an out-of-band channel (eg. NFC, QR).
+ *
+ * <p>References:
+ * NFC AD Forum SSP 1.1 (AD)
+ * {@link https://members.nfc-forum.org//apps/group_public/download.php/24620/NFCForum-AD-BTSSP_1_1.pdf}
+ * Core Specification Supplement (CSS) V9
+ *
+ * <p>There are several BR/EDR Examples
+ *
+ * <p>Negotiated Handover:
+ *   Bluetooth Carrier Configuration Record:
+ *    - OOB Data Length
+ *    - Device Address
+ *    - Class of Device
+ *    - Simple Pairing Hash C
+ *    - Simple Pairing Randomizer R
+ *    - Service Class UUID
+ *    - Bluetooth Local Name
+ *
+ * <p>Static Handover:
+ *   Bluetooth Carrier Configuration Record:
+ *    - OOB Data Length
+ *    - Device Address
+ *    - Class of Device
+ *    - Service Class UUID
+ *    - Bluetooth Local Name
+ *
+ * <p>Simplified Tag Format for Single BT Carrier:
+ *   Bluetooth OOB Data Record:
+ *    - OOB Data Length
+ *    - Device Address
+ *    - Class of Device
+ *    - Service Class UUID
+ *    - Bluetooth Local Name
  *
  * @hide
  */
-public class OobData implements Parcelable {
-    private byte[] mLeBluetoothDeviceAddress;
-    private byte[] mSecurityManagerTk;
-    private byte[] mLeSecureConnectionsConfirmation;
-    private byte[] mLeSecureConnectionsRandom;
+@SystemApi
+public final class OobData implements Parcelable {
 
-    public byte[] getLeBluetoothDeviceAddress() {
-        return mLeBluetoothDeviceAddress;
+    private static final String TAG = "OobData";
+    /** The {@link OobData#mClassicLength} may be. (AD 3.1.1) (CSS 1.6.2) @hide */
+    @SystemApi
+    private static final int OOB_LENGTH_OCTETS = 2;
+    /**
+     * The length for the {@link OobData#mDeviceAddressWithType}(6) and Address Type(1).
+     * (AD 3.1.2) (CSS 1.6.2)
+     * @hide
+     */
+    @SystemApi
+    public static final int DEVICE_ADDRESS_OCTETS = 7;
+    /** The Class of Device is 3 octets. (AD 3.1.3) (CSS 1.6.2) @hide */
+    @SystemApi
+    public static final int CLASS_OF_DEVICE_OCTETS = 3;
+    /** The Confirmation data must be 16 octets. (AD 3.2.2) (CSS 1.6.2) @hide */
+    @SystemApi
+    public static final int CONFIRMATION_OCTETS = 16;
+    /** The Randomizer data must be 16 octets. (AD 3.2.3) (CSS 1.6.2) @hide */
+    @SystemApi
+    public static final int RANDOMIZER_OCTETS = 16;
+    /** The LE Device Role length is 1 octet. (AD 3.3.2) (CSS 1.17) @hide */
+    @SystemApi
+    public static final int LE_DEVICE_ROLE_OCTETS = 1;
+    /** The {@link OobData#mLeTemporaryKey} length. (3.4.1) @hide */
+    @SystemApi
+    public static final int LE_TK_OCTETS = 16;
+    /** The {@link OobData#mLeAppearance} length. (3.4.1) @hide */
+    @SystemApi
+    public static final int LE_APPEARANCE_OCTETS = 2;
+    /** The {@link OobData#mLeFlags} length. (3.4.1) @hide */
+    @SystemApi
+    public static final int LE_DEVICE_FLAG_OCTETS = 1; // 1 octet to hold the 0-4 value.
+
+    // Le Roles
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = { "LE_DEVICE_ROLE_" },
+        value = {
+            LE_DEVICE_ROLE_PERIPHERAL_ONLY,
+            LE_DEVICE_ROLE_CENTRAL_ONLY,
+            LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL,
+            LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL
+        }
+    )
+    public @interface LeRole {}
+
+    /** @hide */
+    @SystemApi
+    public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0x00;
+    /** @hide */
+    @SystemApi
+    public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 0x01;
+    /** @hide */
+    @SystemApi
+    public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 0x02;
+    /** @hide */
+    @SystemApi
+    public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 0x03;
+
+    // Le Flags
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = { "LE_FLAG_" },
+        value = {
+            LE_FLAG_LIMITED_DISCOVERY_MODE,
+            LE_FLAG_GENERAL_DISCOVERY_MODE,
+            LE_FLAG_BREDR_NOT_SUPPORTED,
+            LE_FLAG_SIMULTANEOUS_CONTROLLER,
+            LE_FLAG_SIMULTANEOUS_HOST
+        }
+    )
+    public @interface LeFlag {}
+
+    /** @hide */
+    @SystemApi
+    public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0x00;
+    /** @hide */
+    @SystemApi
+    public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 0x01;
+    /** @hide */
+    @SystemApi
+    public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 0x02;
+    /** @hide */
+    @SystemApi
+    public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 0x03;
+    /** @hide */
+    @SystemApi
+    public static final int LE_FLAG_SIMULTANEOUS_HOST = 0x04;
+
+    /**
+     * Main creation method for creating a Classic version of {@link OobData}.
+     *
+     * <p>This object will allow the caller to call {@link ClassicBuilder#build()}
+     * to build the data object or add any option information to the builder.
+     *
+     * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+     * of data. Data is derived from controller/host stack and is required for pairing OOB.
+     * @param classicLength byte array representing the length of data from 8-65535 across 2
+     * octets (0xXXXX).
+     * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+     * that owns the OOB data. (i.e. the originator) [6 octets]
+     *
+     * @return a Classic Builder instance with all the given data set or null.
+     *
+     * @throws IllegalArgumentException if any of the values fail to be set.
+     * @throws NullPointerException if any argument is null.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public static ClassicBuilder createClassicBuilder(@NonNull byte[] confirmationHash,
+            @NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType) {
+        return new ClassicBuilder(confirmationHash, classicLength, deviceAddressWithType);
     }
 
     /**
-     * Sets the LE Bluetooth Device Address value to be used during LE pairing.
-     * The value shall be 7 bytes. Please see Bluetooth CSSv6, Part A 1.16 for
-     * a detailed description.
+     * Main creation method for creating a LE version of {@link OobData}.
+     *
+     * <p>This object will allow the caller to call {@link LeBuilder#build()}
+     * to build the data object or add any option information to the builder.
+     *
+     * @param deviceAddressWithType the LE device address plus the address type (7 octets);
+     * not null.
+     * @param leDeviceRole whether the device supports Peripheral, Central,
+     * Both including preference; not null. (1 octet)
+     * @param confirmationHash Array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+     * of data. Data is derived from controller/host stack and is
+     * required for pairing OOB.
+     *
+     * <p>Possible LE Device Role Values:
+     * 0x00 Only Peripheral supported
+     * 0x01 Only Central supported
+     * 0x02 Central & Peripheral supported; Peripheral Preferred
+     * 0x03 Only peripheral supported; Central Preferred
+     * 0x04 - 0xFF Reserved
+     *
+     * @return a LeBuilder instance with all the given data set or null.
+     *
+     * @throws IllegalArgumentException if any of the values fail to be set.
+     * @throws NullPointerException if any argument is null.
+     *
+     * @hide
      */
-    public void setLeBluetoothDeviceAddress(byte[] leBluetoothDeviceAddress) {
-        mLeBluetoothDeviceAddress = leBluetoothDeviceAddress;
-    }
-
-    public byte[] getSecurityManagerTk() {
-        return mSecurityManagerTk;
+    @NonNull
+    @SystemApi
+    public static LeBuilder createLeBuilder(@NonNull byte[] confirmationHash,
+            @NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole) {
+        return new LeBuilder(confirmationHash, deviceAddressWithType, leDeviceRole);
     }
 
     /**
-     * Sets the Temporary Key value to be used by the LE Security Manager during
-     * LE pairing. The value shall be 16 bytes. Please see Bluetooth CSSv6,
-     * Part A 1.8 for a detailed description.
+     * Builds an {@link OobData} object and validates that the required combination
+     * of values are present to create the LE specific OobData type.
+     *
+     * @hide
      */
-    public void setSecurityManagerTk(byte[] securityManagerTk) {
-        mSecurityManagerTk = securityManagerTk;
+    @SystemApi
+    public static final class LeBuilder {
+
+        /**
+         * It is recommended that this Hash C is generated anew for each
+         * pairing.
+         *
+         * <p>It should be noted that on passive NFC this isn't possible as the data is static
+         * and immutable.
+         */
+        private byte[] mConfirmationHash = null;
+
+        /**
+         * Optional, but adds more validity to the pairing.
+         *
+         * <p>If not present a value of 0 is assumed.
+         */
+        private byte[] mRandomizerHash = new byte[] {
+            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+        };
+
+        /**
+         * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+         *
+         * <p>This is the name that may be displayed to the device user as part of the UI.
+         */
+        private byte[] mDeviceName = null;
+
+        /**
+         * Sets the Bluetooth Device name to be used for UI purposes.
+         *
+         * <p>Optional attribute.
+         *
+         * @param deviceName byte array representing the name, may be 0 in length, not null.
+         *
+         * @return {@link OobData#ClassicBuilder}
+         *
+         * @throws NullPointerException if deviceName is null.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public LeBuilder setDeviceName(@NonNull byte[] deviceName) {
+            Preconditions.checkNotNull(deviceName);
+            this.mDeviceName = deviceName;
+            return this;
+        }
+
+        /**
+         * The Bluetooth Device Address is the address to which the OOB data belongs.
+         *
+         * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+         *
+         * <p> Address is encoded in Little Endian order.
+         *
+         * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+         */
+        private final byte[] mDeviceAddressWithType;
+
+        /**
+         * During an LE connection establishment, one must be in the Peripheral mode and the other
+         * in the Central role.
+         *
+         * <p>Possible Values:
+         * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+         * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+         * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+         * Peripheral Preferred
+         * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+         * 0x04 - 0xFF Reserved
+         */
+        private final @LeRole int mLeDeviceRole;
+
+        /**
+         * Temporary key value from the Security Manager.
+         *
+         * <p> Must be {@link LE_TK_OCTETS} in size
+         */
+        private byte[] mLeTemporaryKey = null;
+
+        /**
+         * Defines the representation of the external appearance of the device.
+         *
+         * <p>For example, a mouse, remote control, or keyboard.
+         *
+         * <p>Used for visual on discovering device to represent icon/string/etc...
+         */
+        private byte[] mLeAppearance = null;
+
+        /**
+         * Contains which discoverable mode to use, BR/EDR support and capability.
+         *
+         * <p>Possible LE Flags:
+         * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+         * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+         * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+         * LMP Feature Mask Definitions.
+         * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+         * Same Device Capable (Controller).
+         * Bit 49 of LMP Feature Mask Definitions.
+         * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+         * Same Device Capable (Host).
+         * Bit 55 of LMP Feature Mask Definitions.
+         * <b>0x05- 0x07 Reserved</b>
+         */
+        private @LeFlag int mLeFlags = LE_FLAG_GENERAL_DISCOVERY_MODE; // Invalid default
+
+        /**
+         * Constructing an OobData object for use with LE requires
+         * a LE Device Address and LE Device Role as well as the Confirmation
+         * and optionally, the Randomizer, however it is recommended to use.
+         *
+         * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+         * octets of data. Data is derived from controller/host stack and is required for
+         * pairing OOB.
+         * @param deviceAddressWithType 7 bytes containing the 6 byte address with the 1 byte
+         * address type.
+         * @param leDeviceRole indicating device's role and preferences (Central or Peripheral)
+         *
+         * <p>Possible Values:
+         * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+         * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+         * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+         * Peripheral Preferred
+         * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+         * 0x04 - 0xFF Reserved
+         *
+         * @throws IllegalArgumentException if deviceAddressWithType is not
+         *                                  {@link LE_DEVICE_ADDRESS_OCTETS} octets
+         * @throws NullPointerException if any argument is null.
+         */
+        private LeBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] deviceAddressWithType,
+                @LeRole int leDeviceRole) {
+            Preconditions.checkNotNull(confirmationHash);
+            Preconditions.checkNotNull(deviceAddressWithType);
+            if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+                throw new IllegalArgumentException("confirmationHash must be "
+                    + OobData.CONFIRMATION_OCTETS + " octets in length.");
+            }
+            this.mConfirmationHash = confirmationHash;
+            if (deviceAddressWithType.length != OobData.DEVICE_ADDRESS_OCTETS) {
+                throw new IllegalArgumentException("confirmationHash must be "
+                    + OobData.DEVICE_ADDRESS_OCTETS+ " octets in length.");
+            }
+            this.mDeviceAddressWithType = deviceAddressWithType;
+            if (leDeviceRole < LE_DEVICE_ROLE_PERIPHERAL_ONLY
+                    || leDeviceRole > LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL) {
+                throw new IllegalArgumentException("leDeviceRole must be a valid value.");
+            }
+            this.mLeDeviceRole = leDeviceRole;
+        }
+
+        /**
+         * Sets the Temporary Key value to be used by the LE Security Manager during
+         * LE pairing.
+         *
+         * @param leTemporaryKey byte array that shall be 16 bytes. Please see Bluetooth CSSv6,
+         * Part A 1.8 for a detailed description.
+         *
+         * @return {@link OobData#Builder}
+         *
+         * @throws IllegalArgumentException if the leTemporaryKey is an invalid format.
+         * @throws NullinterException if leTemporaryKey is null.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public LeBuilder setLeTemporaryKey(@NonNull byte[] leTemporaryKey) {
+            Preconditions.checkNotNull(leTemporaryKey);
+            if (leTemporaryKey.length != LE_TK_OCTETS) {
+                throw new IllegalArgumentException("leTemporaryKey must be "
+                        + LE_TK_OCTETS + " octets in length.");
+            }
+            this.mLeTemporaryKey = leTemporaryKey;
+            return this;
+        }
+
+        /**
+         * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+         * of data. Data is derived from controller/host stack and is required for pairing OOB.
+         * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+         *
+         * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+         * @throws NullPointerException if randomizerHash is null.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public LeBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+            Preconditions.checkNotNull(randomizerHash);
+            if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+                throw new IllegalArgumentException("randomizerHash must be "
+                    + OobData.RANDOMIZER_OCTETS + " octets in length.");
+            }
+            this.mRandomizerHash = randomizerHash;
+            return this;
+        }
+
+        /**
+         * Sets the LE Flags necessary for the pairing scenario or discovery mode.
+         *
+         * @param leFlags enum value representing the 1 octet of data about discovery modes.
+         *
+         * <p>Possible LE Flags:
+         * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+         * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+         * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+         * LMP Feature Mask Definitions.
+         * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+         * Same Device Capable (Controller) Bit 49 of LMP Feature Mask Definitions.
+         * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+         * Same Device Capable (Host).
+         * Bit 55 of LMP Feature Mask Definitions.
+         * 0x05- 0x07 Reserved
+         *
+         * @throws IllegalArgumentException for invalid flag
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public LeBuilder setLeFlags(@LeFlag int leFlags) {
+            if (leFlags < LE_FLAG_LIMITED_DISCOVERY_MODE || leFlags > LE_FLAG_SIMULTANEOUS_HOST) {
+                throw new IllegalArgumentException("leFlags must be a valid value.");
+            }
+            this.mLeFlags = leFlags;
+            return this;
+        }
+
+        /**
+         * Validates and builds the {@link OobData} object for LE Security.
+         *
+         * @return {@link OobData} with given builder values
+         *
+         * @throws IllegalStateException if either of the 2 required fields were not set.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public OobData build() {
+            final OobData oob =
+                    new OobData(this.mDeviceAddressWithType, this.mLeDeviceRole,
+                            this.mConfirmationHash);
+
+            // If we have values, set them, otherwise use default
+            oob.mLeTemporaryKey =
+                    (this.mLeTemporaryKey != null) ? this.mLeTemporaryKey : oob.mLeTemporaryKey;
+            oob.mLeAppearance = (this.mLeAppearance != null)
+                    ? this.mLeAppearance : oob.mLeAppearance;
+            oob.mLeFlags = (this.mLeFlags != 0xF) ? this.mLeFlags : oob.mLeFlags;
+            oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+            oob.mRandomizerHash = this.mRandomizerHash;
+            return oob;
+        }
     }
 
-    public byte[] getLeSecureConnectionsConfirmation() {
-        return mLeSecureConnectionsConfirmation;
+    /**
+     * Builds an {@link OobData} object and validates that the required combination
+     * of values are present to create the Classic specific OobData type.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class ClassicBuilder {
+        // Used by both Classic and LE
+        /**
+         * It is recommended that this Hash C is generated anew for each
+         * pairing.
+         *
+         * <p>It should be noted that on passive NFC this isn't possible as the data is static
+         * and immutable.
+         *
+         * @hide
+         */
+        private byte[] mConfirmationHash = null;
+
+        /**
+         * Optional, but adds more validity to the pairing.
+         *
+         * <p>If not present a value of 0 is assumed.
+         *
+         * @hide
+         */
+        private byte[] mRandomizerHash = new byte[] {
+            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+        };
+
+        /**
+         * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+         *
+         * <p>This is the name that may be displayed to the device user as part of the UI.
+         *
+         * @hide
+         */
+        private byte[] mDeviceName = null;
+
+        /**
+         * This length value provides the absolute length of total OOB data block used for
+         * Bluetooth BR/EDR
+         *
+         * <p>OOB communication, which includes the length field itself and the Bluetooth
+         * Device Address.
+         *
+         * <p>The minimum length that may be represented in this field is 8.
+         *
+         * @hide
+         */
+        private final byte[] mClassicLength;
+
+        /**
+         * The Bluetooth Device Address is the address to which the OOB data belongs.
+         *
+         * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+         *
+         * <p> Address is encoded in Little Endian order.
+         *
+         * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+         *
+         * @hide
+         */
+        private final byte[] mDeviceAddressWithType;
+
+        /**
+         * Class of Device information is to be used to provide a graphical representation
+         * to the user as part of UI involving operations.
+         *
+         * <p>This is not to be used to determine a particular service can be used.
+         *
+         * <p>The length MUST be {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+         *
+         * @hide
+         */
+        private byte[] mClassOfDevice = null;
+
+        /**
+         * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+         * octets of data. Data is derived from controller/host stack and is required for pairing
+         * OOB.
+         * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+         * of data. Data is derived from controller/host stack and is required
+         * for pairing OOB. Also, randomizerHash may be all 0s or null in which case
+         * it becomes all 0s.
+         * @param classicLength byte array representing the length of data from 8-65535 across 2
+         * octets (0xXXXX). Inclusive of this value in the length.
+         * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+         * that owns the OOB data. (i.e. the originator) [7 octets] this includes the Address Type
+         * as the last octet.
+         *
+         * @throws IllegalArgumentException if any value is not the correct length
+         * @throws NullPointerException if anything passed is null
+         *
+         * @hide
+         */
+        @SystemApi
+        private ClassicBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] classicLength,
+                @NonNull byte[] deviceAddressWithType) {
+            Preconditions.checkNotNull(confirmationHash);
+            Preconditions.checkNotNull(classicLength);
+            Preconditions.checkNotNull(deviceAddressWithType);
+            if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+                throw new IllegalArgumentException("confirmationHash must be "
+                    + OobData.CONFIRMATION_OCTETS + " octets in length.");
+            }
+            this.mConfirmationHash = confirmationHash;
+            if (classicLength.length != OOB_LENGTH_OCTETS) {
+                throw new IllegalArgumentException("classicLength must be "
+                        + OOB_LENGTH_OCTETS + " octets in length.");
+            }
+            this.mClassicLength = classicLength;
+            if (deviceAddressWithType.length != DEVICE_ADDRESS_OCTETS) {
+                throw new IllegalArgumentException("deviceAddressWithType must be "
+                        + DEVICE_ADDRESS_OCTETS + " octets in length.");
+            }
+            this.mDeviceAddressWithType = deviceAddressWithType;
+        }
+
+        /**
+         * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+         * of data. Data is derived from controller/host stack and is required for pairing OOB.
+         * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+         *
+         * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+         * @throws NullPointerException if randomizerHash is null.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public ClassicBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+            Preconditions.checkNotNull(randomizerHash);
+            if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+                throw new IllegalArgumentException("randomizerHash must be "
+                    + OobData.RANDOMIZER_OCTETS + " octets in length.");
+            }
+            this.mRandomizerHash = randomizerHash;
+            return this;
+        }
+
+        /**
+         * Sets the Bluetooth Device name to be used for UI purposes.
+         *
+         * <p>Optional attribute.
+         *
+         * @param deviceName byte array representing the name, may be 0 in length, not null.
+         *
+         * @return {@link OobData#ClassicBuilder}
+         *
+         * @throws NullPointerException if deviceName is null
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public ClassicBuilder setDeviceName(@NonNull byte[] deviceName) {
+            Preconditions.checkNotNull(deviceName);
+            this.mDeviceName = deviceName;
+            return this;
+        }
+
+        /**
+         * Sets the Bluetooth Class of Device; used for UI purposes only.
+         *
+         * <p>Not an indicator of available services!
+         *
+         * <p>Optional attribute.
+         *
+         * @param classOfDevice byte array of {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+         *
+         * @return {@link OobData#ClassicBuilder}
+         *
+         * @throws IllegalArgumentException if length is not equal to
+         * {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+         * @throws NullPointerException if classOfDevice is null.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public ClassicBuilder setClassOfDevice(@NonNull byte[] classOfDevice) {
+            Preconditions.checkNotNull(classOfDevice);
+            if (classOfDevice.length != OobData.CLASS_OF_DEVICE_OCTETS) {
+                throw new IllegalArgumentException("classOfDevice must be "
+                        + OobData.CLASS_OF_DEVICE_OCTETS + " octets in length.");
+            }
+            this.mClassOfDevice = classOfDevice;
+            return this;
+        }
+
+        /**
+         * Validates and builds the {@link OobDat object for Classic Security.
+         *
+         * @return {@link OobData} with previously given builder values.
+         *
+         * @hide
+         */
+        @NonNull
+        @SystemApi
+        public OobData build() {
+            final OobData oob =
+                    new OobData(this.mClassicLength, this.mDeviceAddressWithType,
+                            this.mConfirmationHash);
+            // If we have values, set them, otherwise use default
+            oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+            oob.mClassOfDevice = (this.mClassOfDevice != null)
+                    ? this.mClassOfDevice : oob.mClassOfDevice;
+            oob.mRandomizerHash = this.mRandomizerHash;
+            return oob;
+        }
     }
 
-    public void setLeSecureConnectionsConfirmation(byte[] leSecureConnectionsConfirmation) {
-        mLeSecureConnectionsConfirmation = leSecureConnectionsConfirmation;
+    // Members (Defaults for Optionals must be set or Parceling fails on NPE)
+    // Both
+    private final byte[] mDeviceAddressWithType;
+    private final byte[] mConfirmationHash;
+    private byte[] mRandomizerHash = new byte[] {
+        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+    };
+    // Default the name to "Bluetooth Device"
+    private byte[] mDeviceName = new byte[] {
+        // Bluetooth
+        0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68,
+        // <space>Device
+        0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65
+    };
+
+    // Classic
+    private final byte[] mClassicLength;
+    private byte[] mClassOfDevice = new byte[CLASS_OF_DEVICE_OCTETS];
+
+    // LE
+    private final @LeRole int mLeDeviceRole;
+    private byte[] mLeTemporaryKey = new byte[LE_TK_OCTETS];
+    private byte[] mLeAppearance = new byte[LE_APPEARANCE_OCTETS];
+    private @LeFlag int mLeFlags = LE_FLAG_LIMITED_DISCOVERY_MODE;
+
+    /**
+     * @return byte array representing the MAC address of a bluetooth device.
+     * The Address is 6 octets long with a 1 octet address type associated with the address.
+     *
+     * <p>For classic this will be 6 byte address plus the default of PUBLIC_ADDRESS Address Type.
+     * For LE there are more choices for Address Type.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public byte[] getDeviceAddressWithType() {
+        return mDeviceAddressWithType;
     }
 
-    public byte[] getLeSecureConnectionsRandom() {
-        return mLeSecureConnectionsRandom;
+    /**
+     * @return byte array representing the confirmationHash value
+     * which is used to confirm the identity to the controller.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public byte[] getConfirmationHash() {
+        return mConfirmationHash;
     }
 
-    public void setLeSecureConnectionsRandom(byte[] leSecureConnectionsRandom) {
-        mLeSecureConnectionsRandom = leSecureConnectionsRandom;
+    /**
+     * @return byte array representing the randomizerHash value
+     * which is used to verify the identity of the controller.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public byte[] getRandomizerHash() {
+        return mRandomizerHash;
     }
 
-    public OobData() {
+    /**
+     * @return Device Name used for displaying name in UI.
+     *
+     * <p>Also, this will be populated with the LE Local Name if the data is for LE.
+     *
+     * @hide
+     */
+    @Nullable
+    @SystemApi
+    public byte[] getDeviceName() {
+        return mDeviceName;
+    }
+
+    /**
+     * @return byte array representing the oob data length which is the length
+     * of all of the data including these octets.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public byte[] getClassicLength() {
+        return mClassicLength;
+    }
+
+    /**
+     * @return byte array representing the class of device for UI display.
+     *
+     * <p>Does not indicate services available; for display only.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public byte[] getClassOfDevice() {
+        return mClassOfDevice;
+    }
+
+    /**
+     * @return Temporary Key used for LE pairing.
+     *
+     * @hide
+     */
+    @Nullable
+    @SystemApi
+    public byte[] getLeTemporaryKey() {
+        return mLeTemporaryKey;
+    }
+
+    /**
+     * @return Appearance used for LE pairing. For use in UI situations
+     * when determining what sort of icons or text to display regarding
+     * the device.
+     *
+     * @hide
+     */
+    @Nullable
+    @SystemApi
+    public byte[] getLeAppearance() {
+        return mLeTemporaryKey;
+    }
+
+    /**
+     * @return Flags used to determing discoverable mode to use, BR/EDR Support, and Capability.
+     *
+     * <p>Possible LE Flags:
+     * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+     * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+     * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+     * LMP Feature Mask Definitions.
+     * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+     * Same Device Capable (Controller).
+     * Bit 49 of LMP Feature Mask Definitions.
+     * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+     * Same Device Capable (Host).
+     * Bit 55 of LMP Feature Mask Definitions.
+     * <b>0x05- 0x07 Reserved</b>
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @LeFlag
+    public int getLeFlags() {
+        return mLeFlags;
+    }
+
+    /**
+     * @return the supported and preferred roles of the LE device.
+     *
+     * <p>Possible Values:
+     * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+     * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+     * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+     * Peripheral Preferred
+     * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+     * 0x04 - 0xFF Reserved
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    @LeRole
+    public int getLeDeviceRole() {
+        return mLeDeviceRole;
+    }
+
+    /**
+     * Classic Security Constructor
+     */
+    private OobData(@NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType,
+            @NonNull byte[] confirmationHash) {
+        mClassicLength = classicLength;
+        mDeviceAddressWithType = deviceAddressWithType;
+        mConfirmationHash = confirmationHash;
+        mLeDeviceRole = -1; // Satisfy final
+    }
+
+    /**
+     * LE Security Constructor
+     */
+    private OobData(@NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole,
+            @NonNull byte[] confirmationHash) {
+        mDeviceAddressWithType = deviceAddressWithType;
+        mLeDeviceRole = leDeviceRole;
+        mConfirmationHash = confirmationHash;
+        mClassicLength = new byte[OOB_LENGTH_OCTETS]; // Satisfy final
     }
 
     private OobData(Parcel in) {
-        mLeBluetoothDeviceAddress = in.createByteArray();
-        mSecurityManagerTk = in.createByteArray();
-        mLeSecureConnectionsConfirmation = in.createByteArray();
-        mLeSecureConnectionsRandom = in.createByteArray();
+        // Both
+        mDeviceAddressWithType = in.createByteArray();
+        mConfirmationHash = in.createByteArray();
+        mRandomizerHash = in.createByteArray();
+        mDeviceName = in.createByteArray();
+
+        // Classic
+        mClassicLength = in.createByteArray();
+        mClassOfDevice = in.createByteArray();
+
+        // LE
+        mLeDeviceRole = in.readInt();
+        mLeTemporaryKey = in.createByteArray();
+        mLeAppearance = in.createByteArray();
+        mLeFlags = in.readInt();
     }
 
+    /**
+     * @hide
+     */
     @Override
     public int describeContents() {
         return 0;
     }
 
+    /**
+     * @hide
+     */
     @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeByteArray(mLeBluetoothDeviceAddress);
-        out.writeByteArray(mSecurityManagerTk);
-        out.writeByteArray(mLeSecureConnectionsConfirmation);
-        out.writeByteArray(mLeSecureConnectionsRandom);
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        // Both
+        // Required
+        out.writeByteArray(mDeviceAddressWithType);
+        // Required
+        out.writeByteArray(mConfirmationHash);
+        // Optional
+        out.writeByteArray(mRandomizerHash);
+        // Optional
+        out.writeByteArray(mDeviceName);
+
+        // Classic
+        // Required
+        out.writeByteArray(mClassicLength);
+        // Optional
+        out.writeByteArray(mClassOfDevice);
+
+        // LE
+        // Required
+        out.writeInt(mLeDeviceRole);
+        // Required
+        out.writeByteArray(mLeTemporaryKey);
+        // Optional
+        out.writeByteArray(mLeAppearance);
+        // Optional
+        out.writeInt(mLeFlags);
     }
 
+    // For Parcelable
     public static final @android.annotation.NonNull Parcelable.Creator<OobData> CREATOR =
             new Parcelable.Creator<OobData>() {
         public OobData createFromParcel(Parcel in) {
@@ -108,4 +970,47 @@
             return new OobData[size];
         }
     };
+
+    /**
+     * @return a {@link String} representation of the OobData object.
+     *
+     * @hide
+     */
+    @Override
+    @NonNull
+    public String toString() {
+        return "OobData: \n\t"
+            // Both
+            + "Device Address With Type: " +  toHexString(mDeviceAddressWithType) + "\n\t"
+            + "Confirmation: " + toHexString(mConfirmationHash) + "\n\t"
+            + "Randomizer: " + toHexString(mRandomizerHash) + "\n\t"
+            + "Device Name: " + toHexString(mDeviceName) + "\n\t"
+            // Classic
+            + "OobData Length: " +  toHexString(mClassicLength) + "\n\t"
+            + "Class of Device: " +  toHexString(mClassOfDevice) + "\n\t"
+            // LE
+            + "LE Device Role: " + toHexString(mLeDeviceRole) + "\n\t"
+            + "LE Temporary Key: " + toHexString(mLeTemporaryKey) + "\n\t"
+            + "LE Appearance: " + toHexString(mLeAppearance) + "\n\t"
+            + "LE Flags: " + toHexString(mLeFlags) + "\n\t";
+    }
+
+    @NonNull
+    private String toHexString(@NonNull int b) {
+        return toHexString(new byte[] {(byte) b});
+    }
+
+    @NonNull
+    private String toHexString(@NonNull byte b) {
+        return toHexString(new byte[] {b});
+    }
+
+    @NonNull
+    private String toHexString(@NonNull byte[] array) {
+        StringBuilder builder = new StringBuilder(array.length * 2);
+        for (byte b: array) {
+            builder.append(String.format("%02x", b));
+        }
+        return builder.toString();
+    }
 }
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index f3ecbf6..6ab1975 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,6 +21,7 @@
 import static android.content.ContentResolver.SCHEME_CONTENT;
 import static android.content.ContentResolver.SCHEME_FILE;
 
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.res.AssetFileDescriptor;
@@ -38,6 +39,7 @@
 import android.text.style.URLSpan;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextLinks;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -204,6 +206,7 @@
         Uri mUri;
         // Additional activity info resolved by the system
         ActivityInfo mActivityInfo;
+        private TextLinks mTextLinks;
 
         /** @hide */
         public Item(Item other) {
@@ -332,6 +335,29 @@
         }
 
         /**
+         * Returns the results of text classification run on the raw text contained in this item,
+         * if it was performed, and if any entities were found in the text. Classification is
+         * generally only performed on the first item in clip data, and only if the text is below a
+         * certain length.
+         *
+         * <p>Returns {@code null} if classification was not performed, or if no entities were
+         * found in the text.
+         *
+         * @see ClipDescription#getConfidenceScore(String)
+         */
+        @Nullable
+        public TextLinks getTextLinks() {
+            return mTextLinks;
+        }
+
+        /**
+         * @hide
+         */
+        public void setTextLinks(TextLinks textLinks) {
+            mTextLinks = textLinks;
+        }
+
+        /**
          * Turn this item into text, regardless of the type of data it
          * actually contains.
          *
@@ -1183,6 +1209,7 @@
             dest.writeTypedObject(item.mIntent, flags);
             dest.writeTypedObject(item.mUri, flags);
             dest.writeTypedObject(item.mActivityInfo, flags);
+            dest.writeTypedObject(item.mTextLinks, flags);
         }
     }
 
@@ -1201,8 +1228,10 @@
             Intent intent = in.readTypedObject(Intent.CREATOR);
             Uri uri = in.readTypedObject(Uri.CREATOR);
             ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
+            TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
             Item item = new Item(text, htmlText, intent, uri);
             item.setActivityInfo(info);
+            item.setTextLinks(textLinks);
             mItems.add(item);
         }
     }
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index d48f832..f49362e 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -16,16 +16,25 @@
 
 package android.content;
 
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Map;
 
 /**
  * Meta-data describing the contents of a {@link ClipData}.  Provides enough
@@ -115,12 +124,39 @@
      */
     public static final String EXTRA_ACTIVITY_OPTIONS = "android.intent.extra.ACTIVITY_OPTIONS";
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value =
+            { CLASSIFICATION_NOT_COMPLETE, CLASSIFICATION_NOT_PERFORMED, CLASSIFICATION_COMPLETE})
+    @interface ClassificationStatus {}
+
+    /**
+     * Value returned by {@link #getConfidenceScore(String)} if text classification has not been
+     * completed on the associated clip. This will be always be the case if the clip has not been
+     * copied to clipboard, or if there is no associated clip.
+     */
+    public static final int CLASSIFICATION_NOT_COMPLETE = 1;
+
+    /**
+     * Value returned by {@link #getConfidenceScore(String)} if text classification was not and will
+     * not be performed on the associated clip. This may be the case if the clip does not contain
+     * text in its first item, or if the text is too long.
+     */
+    public static final int CLASSIFICATION_NOT_PERFORMED = 2;
+
+    /**
+     * Value returned by {@link #getConfidenceScore(String)} if text classification has been
+     * completed.
+     */
+    public static final int CLASSIFICATION_COMPLETE = 3;
 
     final CharSequence mLabel;
     private final ArrayList<String> mMimeTypes;
     private PersistableBundle mExtras;
     private long mTimeStamp;
     private boolean mIsStyledText;
+    private final ArrayMap<String, Float> mEntityConfidence = new ArrayMap<>();
+    private int mClassificationStatus = CLASSIFICATION_NOT_COMPLETE;
 
     /**
      * Create a new clip.
@@ -346,6 +382,61 @@
         mIsStyledText = isStyledText;
     }
 
+    /**
+     * Sets the current status of text classification for the associated clip.
+     *
+     * @hide
+     */
+    public void setClassificationStatus(@ClassificationStatus int status) {
+        mClassificationStatus = status;
+    }
+
+    /**
+     * Returns a score indicating confidence that an instance of the given entity is present in the
+     * first item of the clip data, if that item is plain text and text classification has been
+     * performed. The value ranges from 0 (low confidence) to 1 (high confidence). 0 indicates that
+     * the entity was not found in the classified text.
+     *
+     * <p>Entities should be as defined in the {@link TextClassifier} class, such as
+     * {@link TextClassifier#TYPE_ADDRESS}, {@link TextClassifier#TYPE_URL}, or
+     * {@link TextClassifier#TYPE_EMAIL}.
+     *
+     * <p>If the result is positive for any entity, the full classification result as a
+     * {@link TextLinks} object may be obtained using the {@link ClipData.Item#getTextLinks()}
+     * method.
+     *
+     * @throws IllegalStateException if {@link #getClassificationStatus()} is not
+     * {@link #CLASSIFICATION_COMPLETE}
+     */
+    @FloatRange(from = 0.0, to = 1.0)
+    public float getConfidenceScore(@NonNull @TextClassifier.EntityType String entity) {
+        if (mClassificationStatus != CLASSIFICATION_COMPLETE) {
+            throw new IllegalStateException("Classification not complete");
+        }
+        return mEntityConfidence.getOrDefault(entity, 0f);
+    }
+
+    /**
+     * Returns {@link #CLASSIFICATION_COMPLETE} if text classification has been performed on the
+     * associated {@link ClipData}. If this is the case then {@link #getConfidenceScore} may be used
+     * to retrieve information about entities within the text. Otherwise, returns
+     * {@link #CLASSIFICATION_NOT_COMPLETE} if classification has not yet returned results, or
+     * {@link #CLASSIFICATION_NOT_PERFORMED} if classification was not attempted (e.g. because the
+     * text was too long).
+     */
+    public @ClassificationStatus int getClassificationStatus() {
+        return mClassificationStatus;
+    }
+
+    /**
+     * @hide
+     */
+    public void setConfidenceScores(Map<String, Float> confidences) {
+        mEntityConfidence.clear();
+        mEntityConfidence.putAll(confidences);
+        mClassificationStatus = CLASSIFICATION_COMPLETE;
+    }
+
     @Override
     public String toString() {
         StringBuilder b = new StringBuilder(128);
@@ -451,6 +542,23 @@
         dest.writePersistableBundle(mExtras);
         dest.writeLong(mTimeStamp);
         dest.writeBoolean(mIsStyledText);
+        dest.writeInt(mClassificationStatus);
+        dest.writeBundle(confidencesToBundle());
+    }
+
+    private Bundle confidencesToBundle() {
+        Bundle bundle = new Bundle();
+        int size = mEntityConfidence.size();
+        for (int i = 0; i < size; i++) {
+            bundle.putFloat(mEntityConfidence.keyAt(i), mEntityConfidence.valueAt(i));
+        }
+        return bundle;
+    }
+
+    private void readBundleToConfidences(Bundle bundle) {
+        for (String key : bundle.keySet()) {
+            mEntityConfidence.put(key, bundle.getFloat(key));
+        }
     }
 
     ClipDescription(Parcel in) {
@@ -459,6 +567,8 @@
         mExtras = in.readPersistableBundle();
         mTimeStamp = in.readLong();
         mIsStyledText = in.readBoolean();
+        mClassificationStatus = in.readInt();
+        readBundleToConfidences(in.readBundle());
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ClipDescription> CREATOR =
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 49248b5..73b4f62 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -2621,6 +2621,48 @@
         return !TextUtils.isEmpty(uri.getUserInfo());
     }
 
+    /**
+     * Returns the given content URI explicitly associated with the given {@link UserHandle}.
+     *
+     * @param contentUri The content URI to be associated with a user handle.
+     * @param userHandle The user handle with which to associate the URI.
+     *
+     * @throws IllegalArgumentException if
+     * <ul>
+     *  <li>the given URI is not content URI (a content URI has {@link Uri#getScheme} equal to
+     *  {@link ContentResolver.SCHEME_CONTENT}) or</li>
+     *  <li>the given URI is already explicitly associated with a {@link UserHandle}, which is
+     *  different than the given one.</li>
+     *  </ul>
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static Uri createContentUriAsUser(
+            @NonNull Uri contentUri, @NonNull UserHandle userHandle) {
+        if (!ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) {
+            throw new IllegalArgumentException(String.format(
+                "Given URI [%s] is not a content URI: ", contentUri));
+        }
+
+        int userId = userHandle.getIdentifier();
+        if (uriHasUserId(contentUri)) {
+            if (String.valueOf(userId).equals(contentUri.getUserInfo())) {
+                return contentUri;
+            }
+            throw new IllegalArgumentException(String.format(
+                "Given URI [%s] already has a user ID, different from given user handle [%s]",
+                contentUri,
+                userId));
+        }
+
+        Uri.Builder builder = contentUri.buildUpon();
+        builder.encodedAuthority(
+                "" + userHandle.getIdentifier() + "@" + contentUri.getEncodedAuthority());
+        return builder.build();
+    }
+
     /** @hide */
     @UnsupportedAppUsage
     public static Uri maybeAddUserId(Uri uri, int userId) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2523459..f8dd0e1 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3531,6 +3531,7 @@
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
             CONNECTIVITY_SERVICE,
+            PAC_PROXY_SERVICE,
             VCN_MANAGEMENT_SERVICE,
             //@hide: IP_MEMORY_STORE_SERVICE,
             IPSEC_SERVICE,
@@ -4137,6 +4138,17 @@
     public static final String CONNECTIVITY_SERVICE = "connectivity";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a {@link
+     * android.net.PacProxyManager} for handling management of
+     * pac proxy information.
+     *
+     * @see #getSystemService(String)
+     * @see android.net.PacProxyManager
+     * @hide
+     */
+    public static final String PAC_PROXY_SERVICE = "pac_proxy";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a {@link android.net.vcn.VcnManager}
      * for managing Virtual Carrier Networks
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c0384a4..883ffd8 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1901,6 +1901,20 @@
             "android.intent.action.AUTO_REVOKE_PERMISSIONS";
 
     /**
+     * Activity action: Launch UI to manage unused apps (hibernated apps).
+     *
+     * <p>
+     * Input: Nothing.
+     * </p>
+     * <p>
+     * Output: Nothing.
+     * </p>
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_MANAGE_UNUSED_APPS =
+            "android.intent.action.MANAGE_UNUSED_APPS";
+
+    /**
      * Activity action: Launch UI to review permissions for an app.
      * The system uses this intent if permission review for apps not
      * supporting the new runtime permissions model is enabled. In
diff --git a/core/java/android/net/IPacProxyInstalledListener.aidl b/core/java/android/net/IPacProxyInstalledListener.aidl
new file mode 100644
index 0000000..b1f946e
--- /dev/null
+++ b/core/java/android/net/IPacProxyInstalledListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 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.net;
+
+import android.net.Network;
+import android.net.ProxyInfo;
+
+/** {@hide} */
+oneway interface IPacProxyInstalledListener {
+    void onPacProxyInstalled(in Network network, in ProxyInfo proxy);
+}
diff --git a/core/java/android/net/IPacProxyManager.aidl b/core/java/android/net/IPacProxyManager.aidl
new file mode 100644
index 0000000..8f65c56
--- /dev/null
+++ b/core/java/android/net/IPacProxyManager.aidl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2021, 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 perNmissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.IPacProxyInstalledListener;
+import android.net.ProxyInfo;
+
+/** {@hide} */
+interface IPacProxyManager
+{
+    void addListener(IPacProxyInstalledListener listener);
+    void removeListener(IPacProxyInstalledListener listener);
+    void setCurrentProxyScriptUrl(in ProxyInfo proxyInfo);
+}
diff --git a/core/java/android/net/PacProxyManager.java b/core/java/android/net/PacProxyManager.java
new file mode 100644
index 0000000..8f7ad8c
--- /dev/null
+++ b/core/java/android/net/PacProxyManager.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2021 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+@SystemService(Context.PAC_PROXY_SERVICE)
+public class PacProxyManager {
+    private final Context mContext;
+    private final IPacProxyManager mService;
+    @GuardedBy("mListenerMap")
+    private final HashMap<PacProxyInstalledListener, PacProxyInstalledListenerProxy>
+            mListenerMap = new HashMap<>();
+
+    /** @hide */
+    public PacProxyManager(Context context, IPacProxyManager service) {
+        Objects.requireNonNull(service, "missing IPacProxyManager");
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Add a listener to start monitoring events reported by PacProxyService.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void addPacProxyInstalledListener(@NonNull Executor executor,
+            @NonNull PacProxyInstalledListener listener) {
+        try {
+            synchronized (mListenerMap) {
+                final PacProxyInstalledListenerProxy listenerProxy =
+                        new PacProxyInstalledListenerProxy(executor, listener);
+
+                if (null != mListenerMap.putIfAbsent(listener, listenerProxy)) {
+                    throw new IllegalStateException("Listener is already added.");
+                }
+                mService.addListener(listenerProxy);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove the listener to stop monitoring the event of PacProxyInstalledListener.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void removePacProxyInstalledListener(@NonNull PacProxyInstalledListener listener) {
+        try {
+            synchronized (mListenerMap) {
+                final PacProxyInstalledListenerProxy listenerProxy = mListenerMap.remove(listener);
+                if (listenerProxy == null) return;
+                mService.removeListener(listenerProxy);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Updates the PAC Proxy Installer with current Proxy information.
+     */
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.NETWORK_STACK,
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_SETTINGS})
+    public void setCurrentProxyScriptUrl(@Nullable ProxyInfo proxy) {
+        try {
+            mService.setCurrentProxyScriptUrl(proxy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * A callback interface for monitoring changes of PAC proxy information.
+     */
+    public interface PacProxyInstalledListener {
+        /**
+         * Notify that the PAC proxy has been installed. Note that this method will be called with
+         * a ProxyInfo with an empty PAC URL when the PAC proxy is removed.
+         *
+         * This method supports different PAC proxies per-network but not all devices might support
+         * per-network proxies. In that case it will be applied globally.
+         *
+         * @param network the network for which this proxy installed.
+         * @param proxy the installed proxy.
+         */
+        void onPacProxyInstalled(@Nullable Network network, @NonNull ProxyInfo proxy);
+    }
+
+    /**
+     * PacProxyInstalledListener proxy for PacProxyInstalledListener object.
+     * @hide
+     */
+    public class PacProxyInstalledListenerProxy extends IPacProxyInstalledListener.Stub {
+        private final Executor mExecutor;
+        private final PacProxyInstalledListener mListener;
+
+        PacProxyInstalledListenerProxy(Executor executor, PacProxyInstalledListener listener) {
+            mExecutor = executor;
+            mListener = listener;
+        }
+
+        @Override
+        public void onPacProxyInstalled(Network network, ProxyInfo proxy) {
+            Binder.withCleanCallingIdentity(() -> {
+                mExecutor.execute(() -> {
+                    mListener.onPacProxyInstalled(network, proxy);
+                });
+            });
+        }
+    }
+}
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index 326943a..84b7eec 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -51,7 +51,7 @@
                 ServiceManager.getService(PROXY_SERVICE));
         if (mProxyService == null) {
             // Added because of b10267814 where mako is restarting.
-            Log.e(TAG, "PacProxyInstaller: no proxy service");
+            Log.e(TAG, "PacProxyService: no proxy service");
         }
         mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY);
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bfa8f40..85968e3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9886,6 +9886,14 @@
                 "reminder_exp_learning_event_count";
 
         /**
+         * Whether to show clipboard access notifications.
+         *
+         * @hide
+         */
+        public static final String CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS =
+                "clipboard_show_access_notifications";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
diff --git a/core/java/android/service/displayhash/DisplayHashParams.aidl b/core/java/android/service/displayhash/DisplayHashParams.aidl
new file mode 100644
index 0000000..90f9bf1
--- /dev/null
+++ b/core/java/android/service/displayhash/DisplayHashParams.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2021 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.service.displayhash;
+
+parcelable DisplayHashParams;
diff --git a/core/java/android/service/displayhash/DisplayHashParams.java b/core/java/android/service/displayhash/DisplayHashParams.java
new file mode 100644
index 0000000..6a176a33
--- /dev/null
+++ b/core/java/android/service/displayhash/DisplayHashParams.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2021 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.service.displayhash;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.os.Parcelable;
+import android.util.Size;
+import android.view.displayhash.DisplayHashResultCallback;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Information passed from the {@link DisplayHasherService} to system server about how to get the
+ * display data that will be used to generate the {@link android.view.displayhash.DisplayHash}
+ *
+ * @hide
+ */
+@SystemApi
+@DataClass(genAidl = true, genToString = true, genParcelable = true, genHiddenConstructor = true)
+public final class DisplayHashParams implements Parcelable {
+    /**
+     * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+     * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+     * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+     * If {@code null}, the buffer size will not be changed.
+     */
+    @Nullable
+    private final Size mBufferSize;
+
+    /**
+     * Whether the content captured will use filtering when scaling.
+     */
+    private final boolean mBufferScaleWithFiltering;
+
+    /**
+     * Whether the content will be captured in grayscale or color.
+     */
+    private final boolean mGrayscaleBuffer;
+
+    /**
+     * A builder for {@link DisplayHashParams}
+     */
+    public static final class Builder {
+        @Nullable
+        private Size mBufferSize;
+        private boolean mBufferScaleWithFiltering;
+        private boolean mGrayscaleBuffer;
+
+        /**
+         * Creates a new Builder.
+         */
+        public Builder() {
+        }
+
+        /**
+         * The size to scale the buffer to so the hash algorithm can properly generate the hash.
+         */
+        @NonNull
+        public Builder setBufferSize(int w, int h) {
+            mBufferSize = new Size(w, h);
+            return this;
+        }
+
+        /**
+         * Whether the content captured will use filtering when scaling.
+         */
+        @NonNull
+        public Builder setBufferScaleWithFiltering(boolean value) {
+            mBufferScaleWithFiltering = value;
+            return this;
+        }
+
+        /**
+         * Whether the content will be captured in grayscale or color.
+         */
+        @NonNull
+        public Builder setGrayscaleBuffer(boolean value) {
+            mGrayscaleBuffer = value;
+            return this;
+        }
+
+        /** Builds the instance. This builder should not be touched after calling this! */
+        @NonNull
+        public DisplayHashParams build() {
+            return new DisplayHashParams(mBufferSize, mBufferScaleWithFiltering, mGrayscaleBuffer);
+        }
+    }
+
+
+
+    // Code below generated by codegen v1.0.22.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/displayhash/DisplayHashParams.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    /**
+     * Creates a new DisplayHashParams.
+     *
+     * @param bufferSize
+     *   The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+     *   buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+     *   Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+     *   If {@code null}, the buffer size will not be changed.
+     * @param bufferScaleWithFiltering
+     *   Whether the content captured will use filtering when scaling.
+     * @param grayscaleBuffer
+     *   Whether the content will be captured in grayscale or color.
+     * @hide
+     */
+    @DataClass.Generated.Member
+    public DisplayHashParams(
+            @Nullable Size bufferSize,
+            boolean bufferScaleWithFiltering,
+            boolean grayscaleBuffer) {
+        this.mBufferSize = bufferSize;
+        this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+        this.mGrayscaleBuffer = grayscaleBuffer;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    /**
+     * The size to scale the buffer to so the hash algorithm can properly generate the hash. The
+     * buffer given to the {@link DisplayHasherService#onGenerateDisplayHash(byte[], HardwareBuffer,
+     * Rect, String, DisplayHashResultCallback)} will be stretched based on the value set here.
+     * If {@code null}, the buffer size will not be changed.
+     */
+    @DataClass.Generated.Member
+    public @Nullable Size getBufferSize() {
+        return mBufferSize;
+    }
+
+    /**
+     * Whether the content captured will use filtering when scaling.
+     */
+    @DataClass.Generated.Member
+    public boolean isBufferScaleWithFiltering() {
+        return mBufferScaleWithFiltering;
+    }
+
+    /**
+     * Whether the content will be captured in grayscale or color.
+     */
+    @DataClass.Generated.Member
+    public boolean isGrayscaleBuffer() {
+        return mGrayscaleBuffer;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "DisplayHashParams { " +
+                "bufferSize = " + mBufferSize + ", " +
+                "bufferScaleWithFiltering = " + mBufferScaleWithFiltering + ", " +
+                "grayscaleBuffer = " + mGrayscaleBuffer +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mBufferScaleWithFiltering) flg |= 0x2;
+        if (mGrayscaleBuffer) flg |= 0x4;
+        if (mBufferSize != null) flg |= 0x1;
+        dest.writeByte(flg);
+        if (mBufferSize != null) dest.writeSize(mBufferSize);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ DisplayHashParams(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        boolean bufferScaleWithFiltering = (flg & 0x2) != 0;
+        boolean grayscaleBuffer = (flg & 0x4) != 0;
+        Size bufferSize = (flg & 0x1) == 0 ? null : (Size) in.readSize();
+
+        this.mBufferSize = bufferSize;
+        this.mBufferScaleWithFiltering = bufferScaleWithFiltering;
+        this.mGrayscaleBuffer = grayscaleBuffer;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<DisplayHashParams> CREATOR
+            = new Parcelable.Creator<DisplayHashParams>() {
+        @Override
+        public DisplayHashParams[] newArray(int size) {
+            return new DisplayHashParams[size];
+        }
+
+        @Override
+        public DisplayHashParams createFromParcel(@NonNull android.os.Parcel in) {
+            return new DisplayHashParams(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1615565493989L,
+            codegenVersion = "1.0.22",
+            sourceFile = "frameworks/base/core/java/android/service/displayhash/DisplayHashParams.java",
+            inputSignatures = "private final @android.annotation.Nullable android.util.Size mBufferSize\nprivate final  boolean mBufferScaleWithFiltering\nprivate final  boolean mGrayscaleBuffer\nclass DisplayHashParams extends java.lang.Object implements [android.os.Parcelable]\nprivate @android.annotation.Nullable android.util.Size mBufferSize\nprivate  boolean mBufferScaleWithFiltering\nprivate  boolean mGrayscaleBuffer\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferSize(int,int)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setBufferScaleWithFiltering(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams.Builder setGrayscaleBuffer(boolean)\npublic @android.annotation.NonNull android.service.displayhash.DisplayHashParams build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genAidl=true, genToString=true, genParcelable=true, genHiddenConstructor=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/service/displayhash/DisplayHasherService.java b/core/java/android/service/displayhash/DisplayHasherService.java
index 331dbe9..2105d84 100644
--- a/core/java/android/service/displayhash/DisplayHasherService.java
+++ b/core/java/android/service/displayhash/DisplayHasherService.java
@@ -34,6 +34,8 @@
 import android.view.displayhash.DisplayHashResultCallback;
 import android.view.displayhash.VerifiedDisplayHash;
 
+import java.util.Map;
+
 /**
  * A service that handles generating and verify {@link DisplayHash}.
  *
@@ -50,15 +52,6 @@
             "android.service.displayhash.extra.VERIFIED_DISPLAY_HASH";
 
     /**
-     * Manifest metadata key for the resource string array containing the names of all hashing
-     * algorithms provided by the service.
-     *
-     * @hide
-     */
-    public static final String SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS =
-            "android.displayhash.available_algorithms";
-
-    /**
      * The {@link Intent} action that must be declared as handled by a service in its manifest
      * for the system to recognize it as a DisplayHash providing service.
      *
@@ -96,7 +89,7 @@
      * @param buffer        The buffer for the content to generate the hash for.
      * @param bounds        The size and position of the content in window space.
      * @param hashAlgorithm The String for the hashing algorithm to use based values in
-     *                      {@link #SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS)}.
+     *                      {@link #getDisplayHashAlgorithms(RemoteCallback)}.
      * @param callback      The callback to invoke
      *                      {@link DisplayHashResultCallback#onDisplayHashResult(DisplayHash)}
      *                      if successfully generated a DisplayHash or {@link
@@ -108,6 +101,12 @@
             @NonNull String hashAlgorithm, @NonNull DisplayHashResultCallback callback);
 
     /**
+     * Returns a map of supported algorithms and their {@link DisplayHashParams}
+     */
+    @NonNull
+    public abstract Map<String, DisplayHashParams> onGetDisplayHashAlgorithms();
+
+    /**
      * Call to verify that the DisplayHash passed in was generated by the system.
      *
      * @param salt        The salt value to use when verifying the hmac. This should be the
@@ -132,6 +131,15 @@
         callback.sendResult(data);
     }
 
+    private void getDisplayHashAlgorithms(RemoteCallback callback) {
+        Map<String, DisplayHashParams> displayHashParams = onGetDisplayHashAlgorithms();
+        final Bundle data = new Bundle();
+        for (Map.Entry<String, DisplayHashParams> entry : displayHashParams.entrySet()) {
+            data.putParcelable(entry.getKey(), entry.getValue());
+        }
+        callback.sendResult(data);
+    }
+
     private final class DisplayHasherServiceWrapper extends IDisplayHasherService.Stub {
         @Override
         public void generateDisplayHash(byte[] salt, HardwareBuffer buffer, Rect bounds,
@@ -164,5 +172,11 @@
                     obtainMessage(DisplayHasherService::verifyDisplayHash,
                             DisplayHasherService.this, salt, displayHash, callback));
         }
+
+        @Override
+        public void getDisplayHashAlgorithms(RemoteCallback callback) {
+            mHandler.sendMessage(obtainMessage(DisplayHasherService::getDisplayHashAlgorithms,
+                    DisplayHasherService.this, callback));
+        }
     }
 }
diff --git a/core/java/android/service/displayhash/IDisplayHasherService.aidl b/core/java/android/service/displayhash/IDisplayHasherService.aidl
index 236bc28..d9dcdca 100644
--- a/core/java/android/service/displayhash/IDisplayHasherService.aidl
+++ b/core/java/android/service/displayhash/IDisplayHasherService.aidl
@@ -51,4 +51,11 @@
      * @param callback The callback invoked to send back the VerifiedDisplayHash.
      */
     void verifyDisplayHash(in byte[] salt, in DisplayHash displayHash, in RemoteCallback callback);
+
+    /**
+     * Call to get a map of supported algorithms and their {@link DisplayHashParams}
+     *
+     * @param callback The callback invoked to send back the map of algorithms to DisplayHashParams.
+     */
+    void getDisplayHashAlgorithms(in RemoteCallback callback);
 }
diff --git a/core/java/android/view/DragAndDropPermissions.java b/core/java/android/view/DragAndDropPermissions.java
index d47604d..16204d8 100644
--- a/core/java/android/view/DragAndDropPermissions.java
+++ b/core/java/android/view/DragAndDropPermissions.java
@@ -16,12 +16,15 @@
 
 package android.view;
 
+import static java.lang.Integer.toHexString;
+
 import android.app.Activity;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.internal.view.IDragAndDropPermissions;
 
@@ -56,9 +59,28 @@
  */
 public final class DragAndDropPermissions implements Parcelable {
 
-    private final IDragAndDropPermissions mDragAndDropPermissions;
+    private static final String TAG = "DragAndDrop";
+    private static final boolean DEBUG = false;
 
-    private IBinder mTransientToken;
+    /**
+     * Permissions for a drop can be granted in one of two ways:
+     * <ol>
+     *     <li>An app can explicitly request permissions using
+     *     {@link Activity#requestDragAndDropPermissions(DragEvent)}. In this case permissions are
+     *     revoked automatically when then activity is destroyed. See {@link #take(IBinder)}.
+     *     <li>The platform can request permissions on behalf of the app (e.g. in
+     *     {@link android.widget.Editor}). In this case permissions are revoked automatically when
+     *     the app process terminates. See {@link #takeTransient()}.
+     * </ol>
+     *
+     * <p>In order to implement the second case above, we create a static token object here. This
+     * ensures that the token stays alive for the lifetime of the app process, allowing us to
+     * revoke permissions when the app process terminates using {@link IBinder#linkToDeath} in
+     * {@code DragAndDropPermissionsHandler}.
+     */
+    private static IBinder sAppToken;
+
+    private final IDragAndDropPermissions mDragAndDropPermissions;
 
     /**
      * Create a new {@link DragAndDropPermissions} object to control the access permissions for
@@ -81,30 +103,51 @@
     }
 
     /**
-     * Take the permissions and bind their lifetime to the activity.
+     * Take permissions, binding their lifetime to the activity.
+     *
+     * <p>Note: This API is exposed to apps via
+     * {@link Activity#requestDragAndDropPermissions(DragEvent)}.
+     *
      * @param activityToken Binder pointing to an Activity instance to bind the lifetime to.
      * @return True if permissions are successfully taken.
+     *
      * @hide
      */
     public boolean take(IBinder activityToken) {
         try {
+            if (DEBUG) {
+                Log.d(TAG, this + ": calling take() with activity-bound token: "
+                        + toHexString(activityToken.hashCode()));
+            }
             mDragAndDropPermissions.take(activityToken);
         } catch (RemoteException e) {
+            Log.w(TAG, this + ": take() failed with a RemoteException", e);
             return false;
         }
         return true;
     }
 
     /**
-     * Take the permissions. Must call {@link #release} explicitly.
+     * Take permissions transiently. Permissions will be revoked when the app process terminates.
+     *
+     * <p>Note: This API is not exposed to apps.
+     *
      * @return True if permissions are successfully taken.
+     *
      * @hide
      */
     public boolean takeTransient() {
         try {
-            mTransientToken = new Binder();
-            mDragAndDropPermissions.takeTransient(mTransientToken);
+            if (sAppToken == null) {
+                sAppToken = new Binder();
+            }
+            if (DEBUG) {
+                Log.d(TAG, this + ": calling takeTransient() with process-bound token: "
+                        + toHexString(sAppToken.hashCode()));
+            }
+            mDragAndDropPermissions.takeTransient(sAppToken);
         } catch (RemoteException e) {
+            Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
             return false;
         }
         return true;
@@ -116,8 +159,8 @@
     public void release() {
         try {
             mDragAndDropPermissions.release();
-            mTransientToken = null;
         } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -142,11 +185,9 @@
     @Override
     public void writeToParcel(Parcel destination, int flags) {
         destination.writeStrongInterface(mDragAndDropPermissions);
-        destination.writeStrongBinder(mTransientToken);
     }
 
     private DragAndDropPermissions(Parcel in) {
         mDragAndDropPermissions = IDragAndDropPermissions.Stub.asInterface(in.readStrongBinder());
-        mTransientToken = in.readStrongBinder();
     }
 }
diff --git a/core/java/android/view/RoundedCorner.java b/core/java/android/view/RoundedCorner.java
index cc7525b..56b4383 100644
--- a/core/java/android/view/RoundedCorner.java
+++ b/core/java/android/view/RoundedCorner.java
@@ -163,7 +163,7 @@
      * @hide
      */
     public boolean isEmpty() {
-        return mRadius == 0 || mCenter.x == 0 || mCenter.y == 0;
+        return mRadius == 0 || mCenter.x <= 0 || mCenter.y <= 0;
     }
 
     private String getPositionString(@Position int position) {
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index 569c287..623d969 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -181,16 +181,16 @@
         boolean hasRoundedCorner;
         switch (position) {
             case POSITION_TOP_LEFT:
-                hasRoundedCorner = radius > insetTop || radius > insetLeft;
+                hasRoundedCorner = radius > insetTop && radius > insetLeft;
                 break;
             case POSITION_TOP_RIGHT:
-                hasRoundedCorner = radius > insetTop || radius > insetRight;
+                hasRoundedCorner = radius > insetTop && radius > insetRight;
                 break;
             case POSITION_BOTTOM_RIGHT:
-                hasRoundedCorner = radius > insetBottom || radius > insetRight;
+                hasRoundedCorner = radius > insetBottom && radius > insetRight;
                 break;
             case POSITION_BOTTOM_LEFT:
-                hasRoundedCorner = radius > insetBottom || radius > insetLeft;
+                hasRoundedCorner = radius > insetBottom && radius > insetLeft;
                 break;
             default:
                 throw new IllegalArgumentException(
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index c6e5eee..ff4d671 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1308,7 +1308,9 @@
      * @return {@link List} of {@link InputMethodInfo}.
      * @hide
      */
+    @TestApi
     @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+    @NonNull
     public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
         try {
             final Completable.InputMethodInfoList value = Completable.createInputMethodInfoList();
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index 6d5077a..ef50045 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -145,7 +145,7 @@
     @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_EDITTEXT, WIDGET_TYPE_UNSELECTABLE_TEXTVIEW,
             WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW,
             WIDGET_TYPE_CUSTOM_EDITTEXT, WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW,
-            WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_UNKNOWN})
+            WIDGET_TYPE_NOTIFICATION, WIDGET_TYPE_CLIPBOARD, WIDGET_TYPE_UNKNOWN })
     @interface WidgetType {}
 
     /** The widget involved in the text classification context is a standard
@@ -172,6 +172,8 @@
     String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview";
     /** The widget involved in the text classification context is a notification */
     String WIDGET_TYPE_NOTIFICATION = "notification";
+    /** The text classification context is for use with the system clipboard. */
+    String WIDGET_TYPE_CLIPBOARD = "clipboard";
     /** The widget involved in the text classification context is of an unknown/unspecified type. */
     String WIDGET_TYPE_UNKNOWN = "unknown";
 
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 45352e4..c203c790 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -130,7 +130,7 @@
     public @interface EdgeEffectType {
     }
 
-    private static final float DEFAULT_MAX_STRETCH_INTENSITY = 1.5f;
+    private static final float DEFAULT_MAX_STRETCH_INTENSITY = 0.08f;
 
     @SuppressWarnings("UnusedDeclaration")
     private static final String TAG = "EdgeEffect";
@@ -177,6 +177,7 @@
     private long mStartTime;
     private float mDuration;
     private float mStretchIntensity = DEFAULT_MAX_STRETCH_INTENSITY;
+    private float mStretchDistanceFraction = 0.1f;
     private float mStretchDistance = -1f;
 
     private final Interpolator mInterpolator = new DecelerateInterpolator();
@@ -467,7 +468,7 @@
     public void onAbsorb(int velocity) {
         if (mEdgeEffectType == TYPE_STRETCH) {
             mState = STATE_RECEDE;
-            mVelocity = velocity / mHeight;
+            mVelocity = velocity;
             mDistance = 0;
             mStartTime = AnimationUtils.currentAnimationTimeMillis();
         } else {
@@ -655,7 +656,7 @@
                     //  for now leverage placeholder logic if no stretch distance is provided to
                     //  consume the displacement ratio times the minimum of the width or height
                     mStretchDistance > 0 ? mStretchDistance :
-                            (mDisplacement * Math.min(mWidth, mHeight))
+                            (mStretchDistanceFraction * Math.min(mWidth, mHeight))
             );
         }
 
@@ -745,9 +746,9 @@
         final double mDampedFreq = NATURAL_FREQUENCY * Math.sqrt(1 - DAMPING_RATIO * DAMPING_RATIO);
 
         // We're always underdamped, so we can use only those equations:
-        double cosCoeff = mDistance;
+        double cosCoeff = mDistance * mHeight;
         double sinCoeff = (1 / mDampedFreq) * (DAMPING_RATIO * NATURAL_FREQUENCY
-                * mDistance + mVelocity);
+                * mDistance * mHeight + mVelocity);
         double distance = Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
                 * (cosCoeff * Math.cos(mDampedFreq * deltaT)
                 + sinCoeff * Math.sin(mDampedFreq * deltaT));
@@ -755,7 +756,7 @@
                 + Math.pow(Math.E, -DAMPING_RATIO * NATURAL_FREQUENCY * deltaT)
                 * (-mDampedFreq * cosCoeff * Math.sin(mDampedFreq * deltaT)
                 + mDampedFreq * sinCoeff * Math.cos(mDampedFreq * deltaT));
-        mDistance = (float) distance;
+        mDistance = (float) distance / mHeight;
         mVelocity = (float) velocity;
         mStartTime = time;
         if (isAtEquilibrium()) {
@@ -786,9 +787,8 @@
      * considered at rest or false if it is still animating.
      */
     private boolean isAtEquilibrium() {
-        double velocity = mVelocity * mHeight; // in pixels/second
         double displacement = mDistance * mHeight; // in pixels
-        return Math.abs(velocity) < VELOCITY_THRESHOLD
+        return Math.abs(mVelocity) < VELOCITY_THRESHOLD
                 && Math.abs(displacement) < VALUE_THRESHOLD;
     }
 }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index ca89677..238ce85 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2904,9 +2904,6 @@
         } finally {
             mTextView.endBatchEdit();
             mUndoInputFilter.freezeLastEdit();
-            if (permissions != null) {
-                permissions.release();
-            }
         }
     }
 
diff --git a/core/java/android/widget/RemoteCollectionItemsAdapter.java b/core/java/android/widget/RemoteCollectionItemsAdapter.java
new file mode 100644
index 0000000..d843308
--- /dev/null
+++ b/core/java/android/widget/RemoteCollectionItemsAdapter.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2021 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.widget;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.SparseIntArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RemoteViews.ColorResources;
+import android.widget.RemoteViews.InteractionHandler;
+import android.widget.RemoteViews.RemoteCollectionItems;
+
+import com.android.internal.R;
+
+import java.util.stream.IntStream;
+
+/**
+ * List {@link Adapter} backed by a {@link RemoteCollectionItems}.
+ *
+ * @hide
+ */
+class RemoteCollectionItemsAdapter extends BaseAdapter {
+
+    private final int mViewTypeCount;
+
+    private RemoteCollectionItems mItems;
+    private InteractionHandler mInteractionHandler;
+    private ColorResources mColorResources;
+
+    private SparseIntArray mLayoutIdToViewType;
+
+    RemoteCollectionItemsAdapter(
+            @NonNull RemoteCollectionItems items,
+            @NonNull InteractionHandler interactionHandler,
+            @NonNull ColorResources colorResources) {
+        // View type count can never increase after an adapter has been set on a ListView.
+        // Additionally, decreasing it could inhibit view recycling if the count were to back and
+        // forth between 3-2-3-2 for example. Therefore, the view type count, should be fixed for
+        // the lifetime of the adapter.
+        mViewTypeCount = items.getViewTypeCount();
+
+        mItems = items;
+        mInteractionHandler = interactionHandler;
+        mColorResources = colorResources;
+
+        initLayoutIdToViewType();
+    }
+
+    /**
+     * Updates the data for the adapter, allowing recycling of views. Note that if the view type
+     * count has increased, a new adapter should be created and set on the AdapterView instead of
+     * calling this method.
+     */
+    void setData(
+            @NonNull RemoteCollectionItems items,
+            @NonNull InteractionHandler interactionHandler,
+            @NonNull ColorResources colorResources) {
+        if (mViewTypeCount < items.getViewTypeCount()) {
+            throw new IllegalArgumentException(
+                    "RemoteCollectionItemsAdapter cannot increase view type count after creation");
+        }
+
+        mItems = items;
+        mInteractionHandler = interactionHandler;
+        mColorResources = colorResources;
+
+        initLayoutIdToViewType();
+
+        notifyDataSetChanged();
+    }
+
+    private void initLayoutIdToViewType() {
+        SparseIntArray previousLayoutIdToViewType = mLayoutIdToViewType;
+        mLayoutIdToViewType = new SparseIntArray(mViewTypeCount);
+
+        int[] layoutIds = IntStream.range(0, mItems.getItemCount())
+                .map(position -> mItems.getItemView(position).getLayoutId())
+                .distinct()
+                .toArray();
+        if (layoutIds.length > mViewTypeCount) {
+            throw new IllegalArgumentException(
+                    "Collection items uses " + layoutIds.length + " distinct layouts, which is "
+                            + "more than view type count of " + mViewTypeCount);
+        }
+
+        // Tracks whether a layout id (by index, not value) has been assigned a view type.
+        boolean[] processedLayoutIdIndices = new boolean[layoutIds.length];
+        // Tracks whether a view type has been assigned to a layout id already.
+        boolean[] assignedViewTypes = new boolean[mViewTypeCount];
+
+        if (previousLayoutIdToViewType != null) {
+            for (int i = 0; i < layoutIds.length; i++) {
+                int layoutId = layoutIds[i];
+                // Copy over any previously used view types for layout ids in the collection to keep
+                // view types stable across data updates.
+                int previousViewType = previousLayoutIdToViewType.get(layoutId, -1);
+                // Skip this layout id if it wasn't assigned to a view type previously.
+                if (previousViewType < 0) continue;
+
+                mLayoutIdToViewType.put(layoutId, previousViewType);
+                processedLayoutIdIndices[i] = true;
+                assignedViewTypes[previousViewType] = true;
+            }
+        }
+
+        int lastViewType = -1;
+        for (int i = 0; i < layoutIds.length; i++) {
+            // If a view type has already been assigned to the layout id, skip it.
+            if (processedLayoutIdIndices[i]) continue;
+
+            int layoutId = layoutIds[i];
+            // If no view type is assigned for the layout id, choose the next possible value that
+            // isn't already assigned to a layout id. There is guaranteed to be some value available
+            // due to the prior validation logic that count(distinct layout ids) <= viewTypeCount.
+            int viewType = IntStream.range(lastViewType + 1, layoutIds.length)
+                    .filter(type -> !assignedViewTypes[type])
+                    .findFirst()
+                    .orElseThrow(
+                            () -> new IllegalStateException(
+                                    "RemoteCollectionItems has more distinct layout ids than its "
+                                            + "view type count"));
+            mLayoutIdToViewType.put(layoutId, viewType);
+            processedLayoutIdIndices[i] = true;
+            assignedViewTypes[viewType] = true;
+            lastViewType = viewType;
+        }
+    }
+
+    @Override
+    public int getCount() {
+        return mItems.getItemCount();
+    }
+
+    @Override
+    public RemoteViews getItem(int position) {
+        return mItems.getItemView(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return mItems.getItemId(position);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mLayoutIdToViewType.get(mItems.getItemView(position).getLayoutId());
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return mViewTypeCount;
+    }
+
+    @Override
+    public boolean hasStableIds() {
+        return mItems.hasStableIds();
+    }
+
+    @Nullable
+    @Override
+    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+        if (position >= getCount()) return null;
+
+        RemoteViews item = mItems.getItemView(position);
+        item.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD);
+        View reapplyView = getViewToReapply(convertView, item);
+
+        // Reapply the RemoteViews if we can.
+        if (reapplyView != null) {
+            try {
+                item.reapply(
+                        parent.getContext(),
+                        reapplyView,
+                        mInteractionHandler,
+                        null /* size */,
+                        mColorResources);
+                return reapplyView;
+            } catch (RuntimeException e) {
+                // We can't reapply for some reason, we'll fallback to an apply and inflate a
+                // new view.
+            }
+        }
+
+        return item.apply(
+                parent.getContext(),
+                parent,
+                mInteractionHandler,
+                null /* size */,
+                mColorResources);
+    }
+
+    /** Returns {@code convertView} if it can be used to reapply {@code item}, or null otherwise. */
+    @Nullable
+    private static View getViewToReapply(@Nullable View convertView, @NonNull RemoteViews item) {
+        if (convertView == null) return null;
+
+        Object layoutIdTag = convertView.getTag(R.id.widget_frame);
+        if (!(layoutIdTag instanceof Integer)) return null;
+
+        return item.getLayoutId() == (Integer) layoutIdTag ? convertView : null;
+    }
+}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d2f4cea..21589c9 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -28,6 +28,7 @@
 import android.annotation.Px;
 import android.annotation.StringRes;
 import android.annotation.StyleRes;
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
@@ -74,6 +75,7 @@
 import android.util.DisplayMetrics;
 import android.util.IntArray;
 import android.util.Log;
+import android.util.LongArray;
 import android.util.Pair;
 import android.util.SizeF;
 import android.util.SparseIntArray;
@@ -112,6 +114,7 @@
 import java.lang.invoke.MethodType;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -224,6 +227,7 @@
     private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
     private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29;
     private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
+    private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
 
     /** @hide **/
     @IntDef(prefix = "MARGIN_", value = {
@@ -899,6 +903,72 @@
         ArrayList<RemoteViews> list;
     }
 
+    private static class SetRemoteCollectionItemListAdapterAction extends Action {
+        private final RemoteCollectionItems mItems;
+
+        SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
+            viewId = id;
+            mItems = items;
+        }
+
+        SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
+            viewId = parcel.readInt();
+            mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(viewId);
+            dest.writeTypedObject(mItems, flags);
+        }
+
+        @Override
+        public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
+                ColorResources colorResources) throws ActionException {
+            View target = root.findViewById(viewId);
+            if (target == null) return;
+
+            if (!(target instanceof AdapterView)) {
+                Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not "
+                        + "an AdapterView (id: " + viewId + ")");
+                return;
+            }
+
+            AdapterView adapterView = (AdapterView) target;
+            Adapter adapter = adapterView.getAdapter();
+            // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type
+            // count hasn't increased. Note that AbsListView allocates a fixed size array for view
+            // recycling in setAdapter, so we must call setAdapter again if the number of view types
+            // increases.
+            if (adapter instanceof RemoteCollectionItemsAdapter
+                    && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
+                try {
+                    ((RemoteCollectionItemsAdapter) adapter).setData(
+                            mItems, handler, colorResources);
+                } catch (Throwable throwable) {
+                    // setData should never failed with the validation in the items builder, but if
+                    // it does, catch and rethrow.
+                    throw new ActionException(throwable);
+                }
+                return;
+            }
+
+            try {
+                adapterView.setAdapter(
+                        new RemoteCollectionItemsAdapter(mItems, handler, colorResources));
+            } catch (Throwable throwable) {
+                // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
+                // a type error.
+                throw new ActionException(throwable);
+            }
+        }
+
+        @Override
+        public int getActionTag() {
+            return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
+        }
+    }
+
     private class SetRemoteViewsAdapterIntent extends Action {
         public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) {
             this.viewId = id;
@@ -3543,6 +3613,8 @@
                 return new SetOnCheckedChangeResponse(parcel);
             case NIGHT_MODE_REFLECTION_ACTION_TAG:
                 return new NightModeReflectionAction(parcel);
+            case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG:
+                return new SetRemoteCollectionItemListAdapterAction(parcel);
             default:
                 throw new ActionException("Tag " + tag + " not found");
         }
@@ -4215,6 +4287,25 @@
     }
 
     /**
+     * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
+     * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
+     * This is a simpler but less flexible approach to populating collection widgets. Its use is
+     * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
+     * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
+     * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
+     * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
+     *
+     * This API is supported in the compatibility library for previous API levels, see
+     * RemoteViewsCompat.
+     *
+     * @param viewId The id of the {@link AdapterView}.
+     * @param items The items to display in the {@link AdapterView}.
+     */
+    public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) {
+        addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items));
+    }
+
+    /**
      * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}.
      *
      * @param viewId The id of the view to change
@@ -6026,6 +6117,203 @@
         return true;
     }
 
+    /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */
+    public static final class RemoteCollectionItems implements Parcelable {
+        private final long[] mIds;
+        private final RemoteViews[] mViews;
+        private final boolean mHasStableIds;
+        private final int mViewTypeCount;
+
+        RemoteCollectionItems(
+                long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) {
+            mIds = ids;
+            mViews = views;
+            mHasStableIds = hasStableIds;
+            mViewTypeCount = viewTypeCount;
+            if (ids.length != views.length) {
+                throw new IllegalArgumentException(
+                        "RemoteCollectionItems has different number of ids and views");
+            }
+            if (viewTypeCount < 1) {
+                throw new IllegalArgumentException("View type count must be >= 1");
+            }
+            int layoutIdCount = (int) Arrays.stream(views)
+                    .mapToInt(RemoteViews::getLayoutId)
+                    .distinct()
+                    .count();
+            if (layoutIdCount > viewTypeCount) {
+                throw new IllegalArgumentException(
+                        "View type count is set to " + viewTypeCount + ", but the collection "
+                                + "contains " + layoutIdCount + " different layout ids");
+            }
+        }
+
+        RemoteCollectionItems(Parcel in) {
+            int length = in.readInt();
+            mIds = new long[length];
+            in.readLongArray(mIds);
+            mViews = new RemoteViews[length];
+            in.readTypedArray(mViews, RemoteViews.CREATOR);
+            mHasStableIds = in.readBoolean();
+            mViewTypeCount = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mIds.length);
+            dest.writeLongArray(mIds);
+            dest.writeTypedArray(mViews, flags);
+            dest.writeBoolean(mHasStableIds);
+            dest.writeInt(mViewTypeCount);
+        }
+
+        /**
+         * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id
+         * should be considered meaningful across collection updates.
+         *
+         * @return Id for the position.
+         */
+        public long getItemId(int position) {
+            return mIds[position];
+        }
+
+        /**
+         * Returns the {@link RemoteViews} to display at {@code position}.
+         *
+         * @return RemoteViews for the position.
+         */
+        @NonNull
+        public RemoteViews getItemView(int position) {
+            return mViews[position];
+        }
+
+        /**
+         * Returns the number of elements in the collection.
+         *
+         * @return Count of items.
+         */
+        public int getItemCount() {
+            return mIds.length;
+        }
+
+        /**
+         * Returns the view type count for the collection when used in an adapter
+         *
+         * @return Count of view types for the collection when used in an adapter.
+         * @see android.widget.Adapter#getViewTypeCount()
+         */
+        public int getViewTypeCount() {
+            return mViewTypeCount;
+        }
+
+        /**
+         * Indicates whether the item ids are stable across changes to the underlying data.
+         *
+         * @return True if the same id always refers to the same object.
+         * @see android.widget.Adapter#hasStableIds()
+         */
+        public boolean hasStableIds() {
+            return mHasStableIds;
+        }
+
+        @NonNull
+        public static final Creator<RemoteCollectionItems> CREATOR =
+                new Creator<RemoteCollectionItems>() {
+            @NonNull
+            @Override
+            public RemoteCollectionItems createFromParcel(@NonNull Parcel source) {
+                return new RemoteCollectionItems(source);
+            }
+
+            @NonNull
+            @Override
+            public RemoteCollectionItems[] newArray(int size) {
+                return new RemoteCollectionItems[size];
+            }
+        };
+
+        /** Builder class for {@link RemoteCollectionItems} objects.*/
+        public static final class Builder {
+            private final LongArray mIds = new LongArray();
+            private final List<RemoteViews> mViews = new ArrayList<>();
+            private boolean mHasStableIds;
+            private int mViewTypeCount;
+
+            /**
+             * Adds a {@link RemoteViews} to the collection.
+             *
+             * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to
+             *           indicate that ids are stable across changes to the collection.
+             * @param view RemoteViews to display for the row.
+             */
+            @NonNull
+            // Covered by getItemId, getItemView, getItemCount.
+            @SuppressLint("MissingGetterMatchingBuilder")
+            public Builder addItem(long id, @NonNull RemoteViews view) {
+                if (view == null) throw new NullPointerException();
+                if (view.hasMultipleLayouts()) {
+                    throw new IllegalArgumentException(
+                            "RemoteViews used in a RemoteCollectionItems cannot specify separate "
+                                    + "layouts for orientations or sizes.");
+                }
+                mIds.add(id);
+                mViews.add(view);
+                return this;
+            }
+
+            /**
+             * Sets whether the item ids are stable across changes to the underlying data.
+             *
+             * @see android.widget.Adapter#hasStableIds()
+             */
+            @NonNull
+            public Builder setHasStableIds(boolean hasStableIds) {
+                mHasStableIds = hasStableIds;
+                return this;
+            }
+
+            /**
+             * Sets the view type count for the collection when used in an adapter. This can be set
+             * to the maximum number of different layout ids that will be used by RemoteViews in
+             * this collection.
+             *
+             * If this value is not set, then a value will be inferred from the provided items. As
+             * a result, the adapter may need to be recreated when the list is updated with
+             * previously unseen RemoteViews layouts for new items.
+             *
+             * @see android.widget.Adapter#getViewTypeCount()
+             */
+            @NonNull
+            public Builder setViewTypeCount(int viewTypeCount) {
+                mViewTypeCount = viewTypeCount;
+                return this;
+            }
+
+            /** Creates the {@link RemoteCollectionItems} defined by this builder. */
+            @NonNull
+            public RemoteCollectionItems build() {
+                if (mViewTypeCount < 1) {
+                    // If a view type count wasn't specified, set it to be the number of distinct
+                    // layout ids used in the items.
+                    mViewTypeCount = (int) mViews.stream()
+                            .mapToInt(RemoteViews::getLayoutId)
+                            .distinct()
+                            .count();
+                }
+                return new RemoteCollectionItems(
+                        mIds.toArray(),
+                        mViews.toArray(new RemoteViews[0]),
+                        mHasStableIds,
+                        Math.max(mViewTypeCount, 1));
+            }
+        }
+    }
+
     /**
      * Set the ID of the top-level view of the XML layout.
      *
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index d59a415..2464b4a 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -26,6 +26,7 @@
 import android.text.style.SuggestionSpan;
 import android.util.Log;
 import android.util.LruCache;
+import android.util.Range;
 import android.view.textservice.SentenceSuggestionsInfo;
 import android.view.textservice.SpellCheckerSession;
 import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
@@ -62,7 +63,8 @@
     // Pause between each spell check to keep the UI smooth
     private final static int SPELL_PAUSE_DURATION = 400; // milliseconds
 
-    private static final int MIN_SENTENCE_LENGTH = 50;
+    // The maximum length of sentence.
+    private static final int MAX_SENTENCE_LENGTH = WORD_ITERATOR_INTERVAL;
 
     private static final int USE_SPAN_RANGE = -1;
 
@@ -89,7 +91,7 @@
 
     // Shared by all SpellParsers. Cannot be shared with TextView since it may be used
     // concurrently due to the asynchronous nature of onGetSuggestions.
-    private WordIterator mWordIterator;
+    private SentenceIteratorWrapper mSentenceIterator;
 
     @Nullable
     private TextServicesManager mTextServicesManager;
@@ -151,8 +153,9 @@
         resetSession();
 
         if (locale != null) {
-            // Change SpellParsers' wordIterator locale
-            mWordIterator = new WordIterator(locale);
+            // Change SpellParsers' sentenceIterator locale
+            mSentenceIterator = new SentenceIteratorWrapper(
+                    BreakIterator.getSentenceInstance(locale));
         }
 
         // This class is the listener for locale change: warn other locale-aware objects
@@ -306,22 +309,30 @@
             final int start = editable.getSpanStart(spellCheckSpan);
             final int end = editable.getSpanEnd(spellCheckSpan);
 
-            // Do not check this word if the user is currently editing it
-            final boolean isEditing;
+            // Check the span if any of following conditions is met:
+            // - the user is not currently editing it
+            // - or `forceCheckWhenEditingWord` is true.
+            final boolean isNotEditing;
 
             // Defer spell check when typing a word ending with a punctuation like an apostrophe
             // which could end up being a mid-word punctuation.
             if (selectionStart == end + 1
                     && WordIterator.isMidWordPunctuation(
                             mCurrentLocale, Character.codePointBefore(editable, end + 1))) {
-                isEditing = false;
-            } else {
+                isNotEditing = false;
+            } else if (selectionEnd <= start || selectionStart > end) {
                 // Allow the overlap of the cursor and the first boundary of the spell check span
                 // no to skip the spell check of the following word because the
                 // following word will never be spell-checked even if the user finishes composing
-                isEditing = selectionEnd <= start || selectionStart > end;
+                isNotEditing = true;
+            } else {
+                // When cursor is at the end of spell check span, allow spell check if the
+                // character before cursor is a separator.
+                isNotEditing = selectionStart == end
+                        && selectionStart > 0
+                        && isSeparator(Character.codePointBefore(editable, selectionStart));
             }
-            if (start >= 0 && end > start && (forceCheckWhenEditingWord || isEditing)) {
+            if (start >= 0 && end > start && (forceCheckWhenEditingWord || isNotEditing)) {
                 spellCheckSpan.setSpellCheckInProgress(true);
                 final TextInfo textInfo = new TextInfo(editable, start, end, mCookie, mIds[i]);
                 textInfos[textInfosCount++] = textInfo;
@@ -346,6 +357,19 @@
         }
     }
 
+    private static boolean isSeparator(int codepoint) {
+        final int type = Character.getType(codepoint);
+        return ((1 << type) & ((1 << Character.SPACE_SEPARATOR)
+                | (1 << Character.LINE_SEPARATOR)
+                | (1 << Character.PARAGRAPH_SEPARATOR)
+                | (1 << Character.DASH_PUNCTUATION)
+                | (1 << Character.END_PUNCTUATION)
+                | (1 << Character.FINAL_QUOTE_PUNCTUATION)
+                | (1 << Character.INITIAL_QUOTE_PUNCTUATION)
+                | (1 << Character.START_PUNCTUATION)
+                | (1 << Character.OTHER_PUNCTUATION))) != 0;
+    }
+
     private SpellCheckSpan onGetSuggestionsInternal(
             SuggestionsInfo suggestionsInfo, int offset, int length) {
         if (suggestionsInfo == null || suggestionsInfo.getCookie() != mCookie) {
@@ -534,6 +558,60 @@
         mTextView.invalidateRegion(start, end, false /* No cursor involved */);
     }
 
+    /**
+     * A wrapper of sentence iterator which only processes the specified window of the given text.
+     */
+    private static class SentenceIteratorWrapper {
+        private BreakIterator mSentenceIterator;
+        private int mStartOffset;
+        private int mEndOffset;
+
+        SentenceIteratorWrapper(BreakIterator sentenceIterator) {
+            mSentenceIterator = sentenceIterator;
+        }
+
+        /**
+         * Set the char sequence and the text window to process.
+         */
+        public void setCharSequence(CharSequence sequence, int start, int end) {
+            mStartOffset = Math.max(0, start);
+            mEndOffset = Math.min(end, sequence.length());
+            mSentenceIterator.setText(sequence.subSequence(mStartOffset, mEndOffset).toString());
+        }
+
+        /**
+         * See {@link BreakIterator#preceding(int)}
+         */
+        public int preceding(int offset) {
+            if (offset < mStartOffset) {
+                return BreakIterator.DONE;
+            }
+            int result = mSentenceIterator.preceding(offset - mStartOffset);
+            return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+        }
+
+        /**
+         * See {@link BreakIterator#following(int)}
+         */
+        public int following(int offset) {
+            if (offset > mEndOffset) {
+                return BreakIterator.DONE;
+            }
+            int result = mSentenceIterator.following(offset - mStartOffset);
+            return result == BreakIterator.DONE ? BreakIterator.DONE : result + mStartOffset;
+        }
+
+        /**
+         * See {@link BreakIterator#isBoundary(int)}
+         */
+        public boolean isBoundary(int offset) {
+            if (offset < mStartOffset || offset > mEndOffset) {
+                return false;
+            }
+            return mSentenceIterator.isBoundary(offset - mStartOffset);
+        }
+    }
+
     private class SpellParser {
         private Object mRange = new Object();
 
@@ -582,27 +660,15 @@
 
         public void parse() {
             Editable editable = (Editable) mTextView.getText();
-            // Iterate over the newly added text and schedule new SpellCheckSpans
-            final int start =  Math.max(
-                    0, editable.getSpanStart(mRange) - MIN_SENTENCE_LENGTH);
+            final int textChangeStart = editable.getSpanStart(mRange);
+            final int textChangeEnd = editable.getSpanEnd(mRange);
 
-            final int end = editable.getSpanEnd(mRange);
+            Range<Integer> sentenceBoundary = detectSentenceBoundary(editable, textChangeStart,
+                    textChangeEnd);
+            int sentenceStart = sentenceBoundary.getLower();
+            int sentenceEnd = sentenceBoundary.getUpper();
 
-            int wordIteratorWindowEnd = Math.min(end, start + WORD_ITERATOR_INTERVAL);
-            mWordIterator.setCharSequence(editable, start, wordIteratorWindowEnd);
-
-            // Move back to the beginning of the current word, if any
-            int wordStart = mWordIterator.preceding(start);
-            int wordEnd;
-            if (wordStart == BreakIterator.DONE) {
-                wordEnd = mWordIterator.following(start);
-                if (wordEnd != BreakIterator.DONE) {
-                    wordStart = mWordIterator.getBeginning(wordEnd);
-                }
-            } else {
-                wordEnd = mWordIterator.getEnd(wordStart);
-            }
-            if (wordEnd == BreakIterator.DONE) {
+            if (sentenceStart == sentenceEnd) {
                 if (DBG) {
                     Log.i(TAG, "No more spell check.");
                 }
@@ -612,29 +678,16 @@
 
             boolean scheduleOtherSpellCheck = false;
 
-            if (wordIteratorWindowEnd < end) {
+            if (sentenceEnd < textChangeEnd) {
                 if (DBG) {
                     Log.i(TAG, "schedule other spell check.");
                 }
                 // Several batches needed on that region. Cut after last previous word
                 scheduleOtherSpellCheck = true;
             }
-            int spellCheckEnd = mWordIterator.preceding(wordIteratorWindowEnd);
-            boolean correct = spellCheckEnd != BreakIterator.DONE;
-            if (correct) {
-                spellCheckEnd = mWordIterator.getEnd(spellCheckEnd);
-                correct = spellCheckEnd != BreakIterator.DONE;
-            }
-            if (!correct) {
-                if (DBG) {
-                    Log.i(TAG, "Incorrect range span.");
-                }
-                stop();
-                return;
-            }
+            int spellCheckEnd = sentenceEnd;
             do {
-                // TODO: Find the start position of the sentence.
-                int spellCheckStart = wordStart;
+                int spellCheckStart = sentenceStart;
                 boolean createSpellCheckSpan = true;
                 // Cancel or merge overlapped spell check spans
                 for (int i = 0; i < mLength; ++i) {
@@ -671,27 +724,23 @@
                 }
 
                 // Stop spell checking when there are no characters in the range.
-                if (spellCheckEnd < start) {
-                    break;
-                }
                 if (spellCheckEnd <= spellCheckStart) {
                     Log.w(TAG, "Trying to spellcheck invalid region, from "
-                            + start + " to " + end);
+                            + sentenceStart + " to " + spellCheckEnd);
                     break;
                 }
                 if (createSpellCheckSpan) {
                     addSpellCheckSpan(editable, spellCheckStart, spellCheckEnd);
                 }
             } while (false);
-            wordStart = spellCheckEnd;
-
-            if (scheduleOtherSpellCheck && wordStart != BreakIterator.DONE && wordStart <= end) {
+            sentenceStart = spellCheckEnd;
+            if (scheduleOtherSpellCheck && sentenceStart != BreakIterator.DONE
+                    && sentenceStart <= textChangeEnd) {
                 // Update range span: start new spell check from last wordStart
-                setRangeSpan(editable, wordStart, end);
+                setRangeSpan(editable, sentenceStart, textChangeEnd);
             } else {
                 removeRangeSpan(editable);
             }
-
             spellCheck(mForceCheckWhenEditingWord);
         }
 
@@ -708,6 +757,94 @@
         }
     }
 
+    private Range<Integer> detectSentenceBoundary(CharSequence sequence,
+            int textChangeStart, int textChangeEnd) {
+        // Only process a substring of the full text due to performance concern.
+        final int iteratorWindowStart = findSeparator(sequence,
+                Math.max(0, textChangeStart - MAX_SENTENCE_LENGTH),
+                Math.max(0, textChangeStart - 2 * MAX_SENTENCE_LENGTH));
+        final int iteratorWindowEnd = findSeparator(sequence,
+                Math.min(textChangeStart + 2 * MAX_SENTENCE_LENGTH, textChangeEnd),
+                Math.min(textChangeStart + 3 * MAX_SENTENCE_LENGTH, sequence.length()));
+        if (DBG) {
+            Log.d(TAG, "Set iterator window as [" + iteratorWindowStart + ", " + iteratorWindowEnd
+                    + ").");
+        }
+        mSentenceIterator.setCharSequence(sequence, iteratorWindowStart, iteratorWindowEnd);
+
+        // Detect the offset of sentence begin/end on the substring.
+        int sentenceStart = mSentenceIterator.isBoundary(textChangeStart) ? textChangeStart
+                : mSentenceIterator.preceding(textChangeStart);
+        int sentenceEnd = mSentenceIterator.following(sentenceStart);
+        if (sentenceEnd == BreakIterator.DONE) {
+            sentenceEnd = iteratorWindowEnd;
+        }
+        if (DBG) {
+            if (sentenceStart != sentenceEnd) {
+                Log.d(TAG, "Sentence detected [" + sentenceStart + ", " + sentenceEnd + ").");
+            }
+        }
+
+        if (sentenceEnd - sentenceStart <= MAX_SENTENCE_LENGTH) {
+            // Add more sentences until the MAX_SENTENCE_LENGTH limitation is reached.
+            while (sentenceEnd < textChangeEnd) {
+                int nextEnd = mSentenceIterator.following(sentenceEnd);
+                if (nextEnd == BreakIterator.DONE
+                        || nextEnd - sentenceStart > MAX_SENTENCE_LENGTH) {
+                    break;
+                }
+                sentenceEnd = nextEnd;
+            }
+        } else {
+            // If the sentence containing `textChangeStart` is longer than MAX_SENTENCE_LENGTH,
+            // the sentence will be sliced into sub-sentences of about MAX_SENTENCE_LENGTH
+            // characters each. This is done by processing the unchecked part of that sentence :
+            //   [textChangeStart, sentenceEnd)
+            //
+            // - If the `uncheckedLength` is bigger than MAX_SENTENCE_LENGTH, then check the
+            //   [textChangeStart, textChangeStart + MAX_SENTENCE_LENGTH), and leave the rest
+            //   part for the next check.
+            //
+            // - If the `uncheckedLength` is smaller than or equal to MAX_SENTENCE_LENGTH,
+            //   then check [sentenceEnd - MAX_SENTENCE_LENGTH, sentenceEnd).
+            //
+            // The offset should be rounded up to word boundary.
+            int uncheckedLength = sentenceEnd - textChangeStart;
+            if (uncheckedLength > MAX_SENTENCE_LENGTH) {
+                sentenceEnd = findSeparator(sequence, sentenceStart + MAX_SENTENCE_LENGTH,
+                        sentenceEnd);
+                sentenceStart = roundUpToWordStart(sequence, textChangeStart, sentenceStart);
+            } else {
+                sentenceStart = roundUpToWordStart(sequence, sentenceEnd - MAX_SENTENCE_LENGTH,
+                        sentenceStart);
+            }
+        }
+        return new Range(sentenceStart, sentenceEnd);
+    }
+
+    private int roundUpToWordStart(CharSequence sequence, int position, int frontBoundary) {
+        if (isSeparator(sequence.charAt(position))) {
+            return position;
+        }
+        int separator = findSeparator(sequence, position, frontBoundary);
+        return separator != frontBoundary ? separator + 1 : frontBoundary;
+    }
+
+    /**
+     * Search the range [start, end) of sequence and returns the position of the first separator.
+     * If end is smaller than start, do a reverse search.
+     * Returns `end` if no separator is found.
+     */
+    private static int findSeparator(CharSequence sequence, int start, int end) {
+        final int step = start < end ? 1 : -1;
+        for (int i = start; i != end; i += step) {
+            if (isSeparator(sequence.charAt(i))) {
+                return i;
+            }
+        }
+        return end;
+    }
+
     public static boolean haveWordBoundariesChanged(final Editable editable, final int start,
             final int end, final int spanStart, final int spanEnd) {
         final boolean haveWordBoundariesChanged;
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
index c2e1426..7baa53b 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.accessibility.util;
 
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
 
@@ -28,6 +31,10 @@
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE;
 import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_FULL_SCREEN;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE;
+import static com.android.internal.util.FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_WINDOW;
 
 import android.content.ComponentName;
 import android.view.accessibility.AccessibilityManager;
@@ -113,6 +120,19 @@
                 UNKNOWN_STATUS);
     }
 
+    /**
+     * Logs the magnification activated mode and its duration of the usage.
+     * Calls this when the magnification is disabled.
+     *
+     * @param mode The activated magnification mode.
+     * @param duration The duration in milliseconds during the magnification is activated.
+     */
+    public static void logMagnificationUsageState(int mode, long duration) {
+        FrameworkStatsLog.write(FrameworkStatsLog.MAGNIFICATION_USAGE_REPORTED,
+                convertToLoggingMagnificationMode(mode),
+                duration);
+    }
+
     private static int convertToLoggingShortcutType(@ShortcutType int shortcutType) {
         switch (shortcutType) {
             case ACCESSIBILITY_BUTTON:
@@ -127,4 +147,18 @@
         return enabled ? ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__ENABLED
                 : ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED;
     }
+
+    private static int convertToLoggingMagnificationMode(int mode) {
+        switch (mode) {
+            case ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN:
+                return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_FULL_SCREEN;
+            case ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW:
+                return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_WINDOW;
+            case ACCESSIBILITY_MAGNIFICATION_MODE_ALL:
+                return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_ALL;
+
+            default:
+                return MAGNIFICATION_USAGE_REPORTED__ACTIVATED_MODE__MAGNIFICATION_UNKNOWN_MODE;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index d0c807d..783d088 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -221,10 +221,6 @@
         dispatchMessage(mH.obtainMessage(DO_GET_SURROUNDING_TEXT, flags, 0 /* unused */, args));
     }
 
-    public void setImeTemporarilyConsumesInput(boolean imeTemporarilyConsumesInput) {
-         // no-op
-    }
-
     public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
         dispatchMessage(
                 mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
index 6cc5a4a..83345da 100644
--- a/core/java/com/android/internal/widget/CallLayout.java
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -100,7 +100,6 @@
         }
         // TODO(b/179178086): crop/clip the icon to a circle?
         mConversationIconView.setImageIcon(icon);
-        mConversationText.setText(callerName);
     }
 
     @RemotableViewMethod
diff --git a/core/jni/android_net_NetworkUtils.cpp b/core/jni/android_net_NetworkUtils.cpp
index 7508108..a781a37 100644
--- a/core/jni/android_net_NetworkUtils.cpp
+++ b/core/jni/android_net_NetworkUtils.cpp
@@ -52,27 +52,6 @@
 // FrameworkListener limits the size of commands to 4096 bytes.
 constexpr int MAXCMDSIZE = 4096;
 
-static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
-    ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
-    if (detailMessage.get() == NULL) {
-        // Not really much we can do here. We're probably dead in the water,
-        // but let's try to stumble on...
-        env->ExceptionClear();
-    }
-    static jclass errnoExceptionClass =
-            MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
-
-    static jmethodID errnoExceptionCtor =
-            GetMethodIDOrDie(env, errnoExceptionClass,
-            "<init>", "(Ljava/lang/String;I)V");
-
-    jobject exception = env->NewObject(errnoExceptionClass,
-                                       errnoExceptionCtor,
-                                       detailMessage.get(),
-                                       error);
-    env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
 static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     struct sock_filter filter_code[] = {
@@ -150,7 +129,7 @@
     int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
 
     if (fd < 0) {
-        throwErrnoException(env, "resNetworkQuery", -fd);
+        jniThrowErrnoException(env, "resNetworkQuery", -fd);
         return nullptr;
     }
 
@@ -165,7 +144,7 @@
     int fd = resNetworkSend(netId, data, msgLen, flags);
 
     if (fd < 0) {
-        throwErrnoException(env, "resNetworkSend", -fd);
+        jniThrowErrnoException(env, "resNetworkSend", -fd);
         return nullptr;
     }
 
@@ -180,13 +159,13 @@
     int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
     jniSetFileDescriptorOfFD(env, javaFd, -1);
     if (res < 0) {
-        throwErrnoException(env, "resNetworkResult", -res);
+        jniThrowErrnoException(env, "resNetworkResult", -res);
         return nullptr;
     }
 
     jbyteArray answer = env->NewByteArray(res);
     if (answer == nullptr) {
-        throwErrnoException(env, "resNetworkResult", ENOMEM);
+        jniThrowErrnoException(env, "resNetworkResult", ENOMEM);
         return nullptr;
     } else {
         env->SetByteArrayRegion(answer, 0, res,
@@ -208,7 +187,7 @@
 static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
     unsigned dnsNetId = 0;
     if (int res = getNetworkForDns(&dnsNetId) < 0) {
-        throwErrnoException(env, "getDnsNetId", -res);
+        jniThrowErrnoException(env, "getDnsNetId", -res);
         return nullptr;
     }
     bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
@@ -233,8 +212,8 @@
     // Obtain the parameters of the TCP repair window.
     int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size);
     if (rc == -1) {
-      throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
-      return NULL;
+        jniThrowErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
+        return NULL;
     }
 
     struct tcp_info tcpinfo = {};
@@ -244,8 +223,8 @@
     // should be applied to the window size.
     rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size);
     if (rc == -1) {
-      throwErrnoException(env, "getsockopt : TCP_INFO", errno);
-      return NULL;
+        jniThrowErrnoException(env, "getsockopt : TCP_INFO", errno);
+        return NULL;
     }
 
     jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
diff --git a/core/jni/android_os_SharedMemory.cpp b/core/jni/android_os_SharedMemory.cpp
index dc86187..fe375f4 100644
--- a/core/jni/android_os_SharedMemory.cpp
+++ b/core/jni/android_os_SharedMemory.cpp
@@ -35,21 +35,6 @@
 jclass errnoExceptionClass;
 jmethodID errnoExceptionCtor;  // MethodID for ErrnoException.<init>(String,I)
 
-void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
-    ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
-    if (detailMessage.get() == NULL) {
-        // Not really much we can do here. We're probably dead in the water,
-        // but let's try to stumble on...
-        env->ExceptionClear();
-    }
-
-    jobject exception = env->NewObject(errnoExceptionClass,
-                                       errnoExceptionCtor,
-                                       detailMessage.get(),
-                                       error);
-    env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
 jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {
 
     // Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null
@@ -65,7 +50,7 @@
     }
 
     if (fd < 0) {
-        throwErrnoException(env, "SharedMemory_create", err);
+        jniThrowErrnoException(env, "SharedMemory_create", err);
         return nullptr;
     }
 
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index dcfa950..d4b5c2b 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1316,24 +1316,6 @@
     return removeAllProcessGroups();
 }
 
-static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
-    ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName));
-    if (detailMessage.get() == NULL) {
-        // Not really much we can do here. We're probably dead in the water,
-        // but let's try to stumble on...
-        env->ExceptionClear();
-    }
-    static jclass errnoExceptionClass =
-            MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/system/ErrnoException"));
-
-    static jmethodID errnoExceptionCtor =
-            GetMethodIDOrDie(env, errnoExceptionClass, "<init>", "(Ljava/lang/String;I)V");
-
-    jobject exception =
-            env->NewObject(errnoExceptionClass, errnoExceptionCtor, detailMessage.get(), error);
-    env->Throw(reinterpret_cast<jthrowable>(exception));
-}
-
 // Wrapper function to the syscall pidfd_open, which creates a file
 // descriptor that refers to the process whose PID is specified in pid.
 static inline int sys_pidfd_open(pid_t pid, unsigned int flags) {
@@ -1343,7 +1325,7 @@
 static jint android_os_Process_nativePidFdOpen(JNIEnv* env, jobject, jint pid, jint flags) {
     int fd = sys_pidfd_open(pid, flags);
     if (fd < 0) {
-        throwErrnoException(env, "nativePidFdOpen", errno);
+        jniThrowErrnoException(env, "nativePidFdOpen", errno);
         return -1;
     }
     return fd;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 0bed29b..be17d92 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -825,7 +825,7 @@
   PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
              multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
 
-  bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
+  bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, true);
 
   if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
       const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 632d372..dca6002 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -192,6 +192,12 @@
     optional Camera camera = 12;
 
     optional SettingProto carrier_apps_handled = 13 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+    message Clipboard {
+        optional SettingProto show_access_notifications = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+    }
+    optional Clipboard clipboard = 89;
+
     optional SettingProto cmas_additional_broadcast_pkg = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
     repeated SettingProto completed_categories = 15;
     optional SettingProto connectivity_release_pending_intent_delay_ms = 16 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -647,5 +653,5 @@
 
     // Please insert fields in alphabetical order and group them into messages
     // if possible (to avoid reaching the method limit).
-    // Next tag = 89;
+    // Next tag = 90;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 021d691..99ef943 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2775,11 +2775,11 @@
          The app can check whether it has this authorization by calling
          {@link android.provider.Settings#canDrawOverlays
          Settings.canDrawOverlays()}.
-         <p>Protection level: signature|appop|installer|appPredictor|pre23|development -->
+         <p>Protection level: signature|setup|appop|installer|appPredictor|pre23|development -->
     <permission android:name="android.permission.SYSTEM_ALERT_WINDOW"
         android:label="@string/permlab_systemAlertWindow"
         android:description="@string/permdesc_systemAlertWindow"
-        android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" />
+        android:protectionLevel="signature|setup|appop|installer|appPredictor|pre23|development" />
 
     <!-- @SystemApi @hide Allows an application to create windows using the type
          {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY},
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index f92e6d6..b969fa4 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -39,7 +39,7 @@
             android:layout_height="@dimen/notification_expand_button_pill_height"
             android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
             android:gravity="center_vertical"
-            android:paddingLeft="8dp"
+            android:paddingStart="8dp"
             />
 
         <ImageView
diff --git a/core/res/res/layout/notification_template_material_big_base.xml b/core/res/res/layout/notification_template_material_big_base.xml
index 2d1c342..b9a3625 100644
--- a/core/res/res/layout/notification_template_material_big_base.xml
+++ b/core/res/res/layout/notification_template_material_big_base.xml
@@ -27,7 +27,7 @@
         android:id="@+id/notification_action_list_margin_target"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/notification_action_list_height"
+        android:layout_marginBottom="@dimen/notification_content_margin"
         android:orientation="vertical"
         >
 
diff --git a/core/res/res/layout/notification_template_material_big_call.xml b/core/res/res/layout/notification_template_material_big_call.xml
new file mode 100644
index 0000000..1d50467
--- /dev/null
+++ b/core/res/res/layout/notification_template_material_big_call.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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
+  -->
+<com.android.internal.widget.CallLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/status_bar_latest_event_content"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clipChildren="false"
+    android:tag="call"
+    android:theme="@style/Theme.DeviceDefault.Notification"
+    >
+
+    <!-- CallLayout shares visual appearance with ConversationLayout, so shares layouts -->
+    <include layout="@layout/notification_template_conversation_icon_container" />
+
+    <LinearLayout
+        android:id="@+id/notification_action_list_margin_target"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_content_margin"
+        android:orientation="vertical"
+        >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="horizontal"
+            >
+
+            <LinearLayout
+                android:id="@+id/notification_main_column"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_marginStart="@dimen/conversation_content_start"
+                android:orientation="vertical"
+                android:minHeight="68dp"
+                >
+
+                <include
+                    layout="@layout/notification_template_conversation_header"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+
+                <include layout="@layout/notification_template_text_multiline" />
+
+                <include
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/notification_progress_bar_height"
+                    android:layout_marginTop="@dimen/notification_progress_margin_top"
+                    layout="@layout/notification_template_progress"
+                    />
+            </LinearLayout>
+
+            <FrameLayout
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:minWidth="@dimen/notification_content_margin_end"
+                >
+
+                <include
+                    layout="@layout/notification_expand_button"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    />
+
+            </FrameLayout>
+
+        </LinearLayout>
+
+        <ViewStub
+            android:layout="@layout/notification_material_reply_text"
+            android:id="@+id/notification_material_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            />
+
+        <include
+            layout="@layout/notification_template_smart_reply_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginEnd="@dimen/notification_content_margin_end"
+            android:layout_marginTop="@dimen/notification_content_margin"
+            />
+
+        <include layout="@layout/notification_material_action_list" />
+
+    </LinearLayout>
+
+</com.android.internal.widget.CallLayout>
diff --git a/core/res/res/layout/notification_template_material_big_text.xml b/core/res/res/layout/notification_template_material_big_text.xml
index 2954ba2..86e7dec 100644
--- a/core/res/res/layout/notification_template_material_big_text.xml
+++ b/core/res/res/layout/notification_template_material_big_text.xml
@@ -31,7 +31,7 @@
         android:layout_height="wrap_content"
         android:layout_gravity="top"
         android:layout_marginTop="@dimen/notification_content_margin_top"
-        android:layout_marginBottom="@dimen/notification_action_list_height"
+        android:layout_marginBottom="@dimen/notification_content_margin"
         android:clipToPadding="false"
         android:orientation="vertical"
         >
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 7b52ec3..5d9e761 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2021 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,7 +18,6 @@
     android:id="@+id/status_bar_latest_event_content"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
     android:clipChildren="false"
     android:tag="call"
     android:theme="@style/Theme.DeviceDefault.Notification"
@@ -29,58 +27,46 @@
     <include layout="@layout/notification_template_conversation_icon_container" />
 
     <LinearLayout
-        android:id="@+id/notification_action_list_margin_target"
+        xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginBottom="@dimen/notification_action_list_height"
-        android:orientation="vertical"
+        android:layout_height="80dp"
+        android:orientation="horizontal"
         >
 
         <LinearLayout
+            android:id="@+id/notification_main_column"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:layout_gravity="top"
-            android:orientation="horizontal"
+            android:layout_marginStart="@dimen/conversation_content_start"
+            android:orientation="vertical"
+            android:minHeight="68dp"
             >
 
-            <LinearLayout
-                android:id="@+id/notification_main_column"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_weight="1"
-                android:layout_marginStart="@dimen/conversation_content_start"
-                android:layout_marginEnd="@dimen/notification_content_margin_end"
-                android:orientation="vertical"
-                android:minHeight="68dp"
-                >
-
-                <include
-                    layout="@layout/notification_template_conversation_header"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    />
-
-                <include layout="@layout/notification_template_text" />
-
-                <include
-                    android:layout_width="match_parent"
-                    android:layout_height="@dimen/notification_progress_bar_height"
-                    android:layout_marginTop="@dimen/notification_progress_margin_top"
-                    layout="@layout/notification_template_progress"
-                    />
-            </LinearLayout>
-
-            <!-- TODO(b/179178086): remove padding from main column when this is visible -->
-            <include layout="@layout/notification_expand_button"
+            <include
+                layout="@layout/notification_template_conversation_header"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:layout_gravity="top|end"
                 />
 
+            <include layout="@layout/notification_template_text" />
+
         </LinearLayout>
 
-        <include layout="@layout/notification_material_action_list" />
+        <FrameLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:minWidth="@dimen/notification_content_margin_end"
+            >
+
+            <include
+                layout="@layout/notification_expand_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                />
+
+        </FrameLayout>
 
     </LinearLayout>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59fd042..054d108 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5574,6 +5574,8 @@
     <string name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label">On-screen Accessibility Shortcut Chooser</string>
     <!-- Label for triggering hardware accessibility shortcut action [CHAR LIMIT=NONE] -->
     <string name="accessibility_system_action_hardware_a11y_shortcut_label">Accessibility Shortcut</string>
+    <!-- Label for dismissing the notification shade [CHAR LIMIT=NONE] -->
+    <string name="accessibility_system_action_dismiss_notification_shade">Dismiss Notification Shade</string>
     <!-- Accessibility description of caption view -->
     <string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5a7b1fa..b935788 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3115,6 +3115,7 @@
 
   <!-- Notifications: CallStyle -->
   <java-symbol type="layout" name="notification_template_material_call" />
+  <java-symbol type="layout" name="notification_template_material_big_call" />
   <java-symbol type="string" name="call_notification_answer_action" />
   <java-symbol type="string" name="call_notification_decline_action" />
   <java-symbol type="string" name="call_notification_hang_up_action" />
@@ -3986,6 +3987,7 @@
   <java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_label" />
   <java-symbol type="string" name="accessibility_system_action_on_screen_a11y_shortcut_chooser_label" />
   <java-symbol type="string" name="accessibility_system_action_hardware_a11y_shortcut_label" />
+  <java-symbol type="string" name="accessibility_system_action_dismiss_notification_shade" />
 
   <java-symbol type="string" name="accessibility_freeform_caption" />
 
diff --git a/core/tests/coretests/src/android/content/ContentProviderTest.java b/core/tests/coretests/src/android/content/ContentProviderTest.java
index 8895f9b..b282064 100644
--- a/core/tests/coretests/src/android/content/ContentProviderTest.java
+++ b/core/tests/coretests/src/android/content/ContentProviderTest.java
@@ -23,6 +23,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
+import android.os.UserHandle;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -86,4 +87,11 @@
                 mCp.validateIncomingUri(
                         Uri.parse("content://com.example/foo/bar?foo=b//ar#foo=b//ar")));
     }
+
+    @Test
+    public void testCreateContentUriAsUser() {
+        Uri uri = Uri.parse("content://com.example/foo/bar");
+        Uri expectedUri = Uri.parse("content://7@com.example/foo/bar");
+        assertEquals(expectedUri, ContentProvider.createContentUriAsUser(uri, UserHandle.of(7)));
+    }
 }
diff --git a/core/tests/coretests/src/android/view/RoundedCornerTest.java b/core/tests/coretests/src/android/view/RoundedCornerTest.java
index 8eb13bc..4349021 100644
--- a/core/tests/coretests/src/android/view/RoundedCornerTest.java
+++ b/core/tests/coretests/src/android/view/RoundedCornerTest.java
@@ -62,6 +62,13 @@
     }
 
     @Test
+    public void testIsEmpty_negativeCenter() {
+        RoundedCorner roundedCorner =
+                new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT, 1, -2, -3);
+        assertThat(roundedCorner.isEmpty(), is(true));
+    }
+
+    @Test
     public void testEquals() {
         RoundedCorner roundedCorner = new RoundedCorner(
                 RoundedCorner.POSITION_BOTTOM_LEFT, 2, 3, 4);
diff --git a/core/tests/mockingcoretests/src/android/view/DisplayTests.java b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
similarity index 98%
rename from core/tests/mockingcoretests/src/android/view/DisplayTests.java
rename to core/tests/mockingcoretests/src/android/view/DisplayTest.java
index a036db2..f8dd153 100644
--- a/core/tests/mockingcoretests/src/android/view/DisplayTests.java
+++ b/core/tests/mockingcoretests/src/android/view/DisplayTest.java
@@ -56,12 +56,15 @@
  *
  * <p>Build/Install/Run:
  *
- * atest FrameworksMockingCoreTests:android.view.DisplayTests
+ * atest FrameworksMockingCoreTests:android.view.DisplayTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
  */
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @Presubmit
-public class DisplayTests {
+public class DisplayTest {
 
     private static final int APP_WIDTH = 272;
     private static final int APP_HEIGHT = 700;
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index b3a180d..b171599 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -155,6 +155,7 @@
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="media" />
     <assign-permission name="android.permission.REGISTER_MEDIA_RESOURCE_OBSERVER" uid="media" />
     <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="media" />
+    <assign-permission name="android.permission.INTERACT_ACROSS_USERS" uid="media" />
 
     <assign-permission name="android.permission.INTERNET" uid="media" />
 
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 11cb2b7..7c80f70 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -601,7 +601,7 @@
     }
 
     /**
-     * Check whether the caller is the credential management app {@link CredentialManagementApp}.
+     * Check whether the caller is the credential management app {@code CredentialManagementApp}.
      * The credential management app has the ability to manage the user's KeyChain credentials
      * on unmanaged devices.
      *
@@ -611,6 +611,7 @@
      *
      * @return {@code true} if the caller is the credential management app.
      */
+    @WorkerThread
     public static boolean isCredentialManagementApp(@NonNull Context context) {
         boolean isCredentialManagementApp = false;
         try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
@@ -634,6 +635,7 @@
      * @return the credential management app's authentication policy.
      * @throws SecurityException if the caller is not the credential management app.
      */
+    @WorkerThread
     @NonNull
     public static AppUriAuthenticationPolicy getCredentialManagementAppPolicy(
             @NonNull Context context) throws SecurityException {
@@ -665,6 +667,7 @@
      * @hide
      */
     @TestApi
+    @WorkerThread
     @RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
     public static boolean setCredentialManagementApp(@NonNull Context context,
             @NonNull String packageName, @NonNull AppUriAuthenticationPolicy authenticationPolicy) {
@@ -680,13 +683,21 @@
     }
 
     /**
-     * Remove the user's KeyChain credentials on unmanaged devices.
+     * Called by the credential management app {@code CredentialManagementApp} to unregister as
+     * the credential management app and stop managing the user's credentials.
+     *
+     * <p> All credentials previously installed by the credential management app will be removed
+     * from the user's device.
+     *
+     * <p> An app holding {@code MANAGE_CREDENTIAL_MANAGEMENT_APP} permission can also call this
+     * method to remove the current credential management app, even if it's not the current
+     * credential management app itself.
      *
      * @return {@code true} if the credential management app was successfully removed.
-     * @hide
      */
-    @TestApi
-    @RequiresPermission(Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP)
+    @WorkerThread
+    @RequiresPermission(value = Manifest.permission.MANAGE_CREDENTIAL_MANAGEMENT_APP,
+            conditional = true)
     public static boolean removeCredentialManagementApp(@NonNull Context context) {
         try (KeyChainConnection keyChainConnection = KeyChain.bind(context)) {
             keyChainConnection.getService().removeCredentialManagementApp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index cb54021..3708e15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -119,7 +119,7 @@
                 listeners.get(i).onDisplayAreaAppeared(displayAreaInfo);
             }
         }
-        applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+        applyConfigChangesToContext(displayAreaInfo);
     }
 
     @Override
@@ -161,24 +161,27 @@
                 listeners.get(i).onDisplayAreaInfoChanged(displayAreaInfo);
             }
         }
-        applyConfigChangesToContext(displayId, displayAreaInfo.configuration);
+        applyConfigChangesToContext(displayAreaInfo);
     }
 
     /**
-     * Applies the {@link Configuration} to the {@link DisplayAreaContext} specified by
-     * {@code displayId}.
-     *
-     * @param displayId The ID of the {@link Display} which the {@link DisplayAreaContext} is
-     *                  associated with
-     * @param newConfig The propagated configuration
+     * Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
+     * {@link DisplayAreaInfo#displayId}.
      */
-    private void applyConfigChangesToContext(int displayId, @NonNull Configuration newConfig) {
+    private void applyConfigChangesToContext(@NonNull DisplayAreaInfo displayAreaInfo) {
+        final int displayId = displayAreaInfo.displayId;
+        final Display display = mContext.getSystemService(DisplayManager.class)
+                .getDisplay(displayId);
+        if (display == null) {
+            throw new UnsupportedOperationException("The display #" + displayId + " is invalid."
+                    + "displayAreaInfo:" + displayAreaInfo);
+        }
         DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
         if (daContext == null) {
-            daContext = new DisplayAreaContext(mContext, displayId);
+            daContext = new DisplayAreaContext(mContext, display);
             mDisplayAreaContexts.put(displayId, daContext);
         }
-        daContext.updateConfigurationChanges(newConfig);
+        daContext.updateConfigurationChanges(displayAreaInfo.configuration);
     }
 
     /**
@@ -228,10 +231,8 @@
         private final IBinder mToken = new Binder();
         private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
 
-        public DisplayAreaContext(@NonNull Context context, int displayId) {
+        public DisplayAreaContext(@NonNull Context context, @NonNull Display display) {
             super(null);
-            final Display display = context.getSystemService(DisplayManager.class)
-                    .getDisplay(displayId);
             attachBaseContext(context.createTokenContext(mToken, display));
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 14fbaac..50d8098 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -36,6 +36,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -149,11 +150,12 @@
         context = displayContext;
         if (theme != context.getThemeResId() || labelRes != 0) {
             try {
-                context = context.createPackageContext(
-                        activityInfo.packageName, CONTEXT_RESTRICTED);
+                context = context.createPackageContextAsUser(activityInfo.packageName,
+                        CONTEXT_RESTRICTED, UserHandle.of(taskInfo.userId));
                 context.setTheme(theme);
             } catch (PackageManager.NameNotFoundException e) {
-                // Ignore
+                Slog.w(TAG, "Failed creating package context with package name "
+                        + activityInfo.packageName + " for user " + taskInfo.userId, e);
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
index afaf33a..c7a1c9a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipCloseWithSwipeTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -51,40 +51,40 @@
             }
         }
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun pipWindowBecomesInvisible() = super.pipWindowBecomesInvisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerRotatesScales() =
         testSpec.statusBarLayerRotatesScales(testSpec.config.startRotation, Surface.ROTATION_0)
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun noUncoveredRegions() = super.noUncoveredRegions()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
index 3309e10..bf148bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
 import android.view.Surface
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
@@ -78,7 +78,7 @@
             }
         }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipWindowInsideDisplayBounds() {
         testSpec.assertWm {
@@ -86,7 +86,7 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun bothAppWindowsVisible() {
         testSpec.assertWmEnd {
@@ -96,15 +96,15 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarWindowIsAlwaysVisible() = super.statusBarWindowIsAlwaysVisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun pipLayerInsideDisplayBounds() {
         testSpec.assertLayers {
@@ -112,7 +112,7 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     fun bothAppLayersVisible() {
         testSpec.assertLayersEnd {
@@ -121,11 +121,11 @@
         }
     }
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun navBarLayerIsAlwaysVisible() = super.navBarLayerIsAlwaysVisible()
 
-    @Postsubmit
+    @Presubmit
     @Test
     override fun statusBarLayerIsAlwaysVisible() = super.statusBarLayerIsAlwaysVisible()
 
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index e93824d..0112686 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -336,6 +336,7 @@
     GET_DEV_PROC(ResetCommandBuffer);
     GET_DEV_PROC(ResetFences);
     GET_DEV_PROC(WaitForFences);
+    GET_DEV_PROC(FrameBoundaryANDROID);
 }
 
 void VulkanManager::initialize() {
@@ -516,6 +517,25 @@
     if (semaphore != VK_NULL_HANDLE) {
         if (submitted == GrSemaphoresSubmitted::kYes) {
             mSwapSemaphore = semaphore;
+            if (mFrameBoundaryANDROID) {
+                // retrieve VkImage used as render target
+                VkImage image = VK_NULL_HANDLE;
+                GrBackendRenderTarget backendRenderTarget =
+                        surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
+                if (backendRenderTarget.isValid()) {
+                    GrVkImageInfo info;
+                    if (backendRenderTarget.getVkImageInfo(&info)) {
+                        image = info.fImage;
+                    } else {
+                        ALOGE("Frame boundary: backend is not vulkan");
+                    }
+                } else {
+                    ALOGE("Frame boundary: invalid backend render target");
+                }
+                // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+                // it won't wait on it.
+                mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
+            }
         } else {
             destroy_semaphore(mDestroySemaphoreContext);
             mDestroySemaphoreContext = nullptr;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 0912369..7b5fe19 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -31,6 +31,21 @@
 #include <vk/GrVkExtensions.h>
 #include <vulkan/vulkan.h>
 
+// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI
+// (https://github.com/google/agi) to enable profiling of apps rendering via
+// HWUI. This extension is not defined in Khronos, hence the need to declare it
+// manually here. There's a superseding extension (VK_EXT_frame_boundary) being
+// discussed in Khronos, but in the meantime we use the bespoke
+// VK_ANDROID_frame_boundary. This is a device extension that is implemented by
+// AGI's Vulkan capture layer, such that it is only supported by devices when
+// AGI is doing a capture of the app.
+//
+// TODO(b/182165045): use the Khronos blessed VK_EXT_frame_boudary once it has
+// landed in the spec.
+typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore semaphore,
+                                                    VkImage image);
+#define VK_ANDROID_FRAME_BOUNDARY_EXTENSION_NAME "VK_ANDROID_frame_boundary"
+
 #include "Frame.h"
 #include "IRenderPipeline.h"
 #include "VulkanSurface.h"
@@ -160,6 +175,7 @@
     VkPtr<PFN_vkDestroyFence> mDestroyFence;
     VkPtr<PFN_vkWaitForFences> mWaitForFences;
     VkPtr<PFN_vkResetFences> mResetFences;
+    VkPtr<PFN_vkFrameBoundaryANDROID> mFrameBoundaryANDROID;
 
     VkInstance mInstance = VK_NULL_HANDLE;
     VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 548b415..ae64c02 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -21,7 +21,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
-import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.Application;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -2937,9 +2936,7 @@
      * @return a {@link PlaybackComponent} associated with the session,
      * or {@code null} if the session is closed or does not exist.
      * @see PlaybackComponent
-     * @hide
      */
-    @TestApi
     @Nullable
     public PlaybackComponent getPlaybackComponent(@NonNull byte[] sessionId) {
         if (sessionId == null) {
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index b4db305..5f44b62 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -21,6 +21,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -89,6 +90,7 @@
     private final CopyOnWriteArrayList<ControllerCreationRequest> mControllerCreationRequests =
             new CopyOnWriteArrayList<>();
 
+    // TODO: Specify the fields that are only used (or not used) by system media router.
     private final String mClientPackageName;
     private final ManagerCallback mManagerCallback;
 
@@ -132,18 +134,34 @@
     }
 
     /**
-     * Gets an instance of the media router which controls the app's media routing.
+     * Gets an instance of the system media router which controls the app's media routing.
      * Returns {@code null} if the given package name is invalid.
+     * There are several things to note when using the media routers created with this method.
      * <p>
-     * Note: For media routers created with this method, the discovery preference passed to
-     * {@link #registerRouteCallback} will have no effect. The callback will be called accordingly
-     * with the client app's discovery preference. Therefore, it is recommended to pass
+     * First of all, the discovery preference passed to {@link #registerRouteCallback}
+     * will have no effect. The callback will be called accordingly with the client app's
+     * discovery preference. Therefore, it is recommended to pass
      * {@link RouteDiscoveryPreference#EMPTY} there.
+     * <p>
+     * Also, do not keep/compare the instances of the {@link RoutingController}, since they are
+     * always newly created with the latest session information whenever below methods are called:
+     * <ul>
+     * <li> {@link #getControllers()} </li>
+     * <li> {@link #getController(String)}} </li>
+     * <li> {@link TransferCallback#onTransfer(RoutingController, RoutingController)} </li>
+     * <li> {@link TransferCallback#onStop(RoutingController)} </li>
+     * <li> {@link ControllerCallback#onControllerUpdated(RoutingController)} </li>
+     * </ul>
+     * Therefore, in order to track the current routing status, keep the controller's ID instead,
+     * and use {@link #getController(String)} and {@link #getSystemController()} for
+     * getting controllers.
+     * <p>
+     * Finally, it will have no effect to call {@link #setOnGetControllerHintsListener}.
      *
      * @param clientPackageName the package name of the app to control
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     @Nullable
     public static MediaRouter2 getInstance(@NonNull Context context,
             @NonNull String clientPackageName) {
@@ -168,12 +186,40 @@
                 instance = new MediaRouter2(context, clientPackageName);
                 sSystemMediaRouter2Map.put(clientPackageName, instance);
                 // TODO: Remove router instance once it is not needed.
-                instance.registerManagerCallback();
+                instance.registerManagerCallbackForSystemRouter();
             }
             return instance;
         }
     }
 
+    /**
+     * Starts scanning remote routes.
+     * Note that calling start/stopScan is applied to all system routers in the same process.
+     *
+     * @see #stopScan()
+     * @hide
+     */
+    @SystemApi
+    public void startScan() {
+        if (isSystemRouter()) {
+            sManager.startScan();
+        }
+    }
+
+    /**
+     * Stops scanning remote routes to reduce resource consumption.
+     * Note that calling start/stopScan is applied to all system routers in the same process.
+     *
+     * @see #startScan()
+     * @hide
+     */
+    @SystemApi
+    public void stopScan() {
+        if (isSystemRouter()) {
+            sManager.stopScan();
+        }
+    }
+
     private MediaRouter2(Context appContext) {
         mContext = appContext;
         mMediaRouterService = IMediaRouterService.Stub.asInterface(
@@ -209,13 +255,15 @@
     }
 
     private MediaRouter2(Context context, String clientPackageName) {
+        mContext = context;
         mClientPackageName = clientPackageName;
         mManagerCallback = new ManagerCallback();
-        mContext = context;
-        mMediaRouterService = null;
-        mPackageName = null;
         mHandler = new Handler(Looper.getMainLooper());
-        mSystemController = null;
+        mSystemController = new SystemRoutingController(sManager.getSystemRoutingSession());
+        mMediaRouterService = null; // TODO: Make this non-null and check permission.
+
+        // Only used by non-system MediaRouter2.
+        mPackageName = null;
     }
 
     /**
@@ -240,7 +288,7 @@
      * @see #getInstance(Context, String)
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     @Nullable
     public String getClientPackageName() {
         return mClientPackageName;
@@ -358,7 +406,8 @@
      *
      * @hide
      */
-    //@SystemApi
+    @SystemApi
+    @NonNull
     public List<MediaRoute2Info> getAllRoutes() {
         if (isSystemRouter()) {
             return sManager.getAllRoutes();
@@ -377,10 +426,6 @@
      */
     @NonNull
     public List<MediaRoute2Info> getRoutes() {
-        if (isSystemRouter()) {
-            return sManager.getAvailableRoutes(mClientPackageName);
-        }
-
         synchronized (mLock) {
             if (mShouldUpdateRoutes) {
                 mShouldUpdateRoutes = false;
@@ -474,6 +519,9 @@
      *                 {@code null} for unset.
      */
     public void setOnGetControllerHintsListener(@Nullable OnGetControllerHintsListener listener) {
+        if (isSystemRouter()) {
+            return;
+        }
         mOnGetControllerHintsListener = listener;
     }
 
@@ -519,7 +567,7 @@
      * @param route the route you want to transfer the media to.
      * @hide
      */
-    //@SystemApi
+    @SystemApi
     public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
         if (isSystemRouter()) {
             sManager.transfer(controller.getRoutingSessionInfo(), route);
@@ -606,6 +654,23 @@
     }
 
     /**
+     * Gets a {@link RoutingController} whose ID is equal to the given ID.
+     * Returns {@code null} if there is no matching controller.
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public RoutingController getController(@NonNull String id) {
+        Objects.requireNonNull(id, "id must not be null");
+        for (RoutingController controller : getControllers()) {
+            if (TextUtils.equals(id, controller.getId())) {
+                return controller;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Gets the list of currently active {@link RoutingController routing controllers} on which
      * media can be played.
      * <p>
@@ -614,15 +679,25 @@
      */
     @NonNull
     public List<RoutingController> getControllers() {
-        // TODO: Do not create the controller instances every time,
-        //       Instead, update the list using the sessions' ID and session related callbacks.
+        List<RoutingController> result = new ArrayList<>();
+
         if (isSystemRouter()) {
-            return sManager.getRoutingSessions(mClientPackageName).stream()
-                    .map(info -> new RoutingController(info))
-                    .collect(Collectors.toList());
+            // Unlike non-system MediaRouter2, controller instances cannot be kept,
+            // since the transfer events initiated from other apps will not come through manager.
+            List<RoutingSessionInfo> sessions = sManager.getRoutingSessions(mClientPackageName);
+            for (RoutingSessionInfo session : sessions) {
+                RoutingController controller;
+                if (session.isSystemSession()) {
+                    mSystemController.setRoutingSessionInfo(session);
+                    controller = mSystemController;
+                } else {
+                    controller = new RoutingController(session);
+                }
+                result.add(controller);
+            }
+            return result;
         }
 
-        List<RoutingController> result = new ArrayList<>();
         result.add(0, mSystemController);
         synchronized (mLock) {
             result.addAll(mNonSystemRoutingControllers.values());
@@ -639,9 +714,15 @@
      * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
      * @hide
      */
+    @SystemApi
     public void setRouteVolume(@NonNull MediaRoute2Info route, int volume) {
         Objects.requireNonNull(route, "route must not be null");
 
+        if (isSystemRouter()) {
+            sManager.setRouteVolume(route, volume);
+            return;
+        }
+
         MediaRouter2Stub stub;
         synchronized (mLock) {
             stub = mStub;
@@ -928,8 +1009,9 @@
 
     /**
      * Registers {@link MediaRouter2Manager.Callback} for getting events.
+     * Should only used for system media routers.
      */
-    private void registerManagerCallback() {
+    private void registerManagerCallbackForSystemRouter() {
         // Using direct executor here, since MediaRouter2Manager also posts to the main handler.
         sManager.registerCallback(Runnable::run, mManagerCallback);
     }
@@ -941,6 +1023,16 @@
                 .collect(Collectors.toList());
     }
 
+    private void updateAllRoutesFromManager() {
+        synchronized (mLock) {
+            mRoutes.clear();
+            for (MediaRoute2Info route : sManager.getAllRoutes()) {
+                mRoutes.put(route.getId(), route);
+            }
+            mShouldUpdateRoutes = true;
+        }
+    }
+
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (RouteCallbackRecord record: mRouteCallbackRecords) {
             List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
@@ -971,6 +1063,13 @@
         }
     }
 
+    private void notifyPreferredFeaturesChanged(List<String> features) {
+        for (RouteCallbackRecord record: mRouteCallbackRecords) {
+            record.mExecutor.execute(
+                    () -> record.mRouteCallback.onPreferredFeaturesChanged(features));
+        }
+    }
+
     private void notifyTransfer(RoutingController oldController, RoutingController newController) {
         for (TransferCallbackRecord record: mTransferCallbackRecords) {
             record.mExecutor.execute(
@@ -1024,6 +1123,17 @@
          * @param routes the list of routes that have been changed. It's never empty.
          */
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
+
+        /**
+         * Called when the client app's preferred features are changed.
+         * When this is called, it is recommended to {@link #getRoutes()} to get the routes
+         * that are currently available to the app.
+         *
+         * @param preferredFeatures the new preferred features set by the application
+         * @hide
+         */
+        @SystemApi
+        public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
     }
 
     /**
@@ -1131,6 +1241,11 @@
             mState = CONTROLLER_STATE_ACTIVE;
         }
 
+        RoutingController(@NonNull RoutingSessionInfo sessionInfo, int state) {
+            mSessionInfo = sessionInfo;
+            mState = state;
+        }
+
         /**
          * @return the ID of the controller. It is globally unique.
          */
@@ -1291,6 +1406,11 @@
                 return;
             }
 
+            if (isSystemRouter()) {
+                sManager.selectRoute(getRoutingSessionInfo(), route);
+                return;
+            }
+
             MediaRouter2Stub stub;
             synchronized (mLock) {
                 stub = mStub;
@@ -1338,6 +1458,11 @@
                 return;
             }
 
+            if (isSystemRouter()) {
+                sManager.deselectRoute(getRoutingSessionInfo(), route);
+                return;
+            }
+
             MediaRouter2Stub stub;
             synchronized (mLock) {
                 stub = mStub;
@@ -1407,6 +1532,12 @@
                 Log.w(TAG, "setVolume: Called on released controller. Ignoring.");
                 return;
             }
+
+            if (isSystemRouter()) {
+                sManager.setSessionVolume(getRoutingSessionInfo(), volume);
+                return;
+            }
+
             MediaRouter2Stub stub;
             synchronized (mLock) {
                 stub = mStub;
@@ -1471,6 +1602,11 @@
                 mState = CONTROLLER_STATE_RELEASED;
             }
 
+            if (isSystemRouter()) {
+                sManager.releaseSession(getRoutingSessionInfo());
+                return;
+            }
+
             synchronized (mLock) {
                 mNonSystemRoutingControllers.remove(getId(), this);
 
@@ -1539,6 +1675,12 @@
         }
 
         private List<MediaRoute2Info> getRoutesWithIds(List<String> routeIds) {
+            if (isSystemRouter()) {
+                return getRoutes().stream()
+                        .filter(r -> routeIds.contains(r.getId()))
+                        .collect(Collectors.toList());
+            }
+
             synchronized (mLock) {
                 return routeIds.stream().map(mRoutes::get)
                         .filter(Objects::nonNull)
@@ -1722,12 +1864,17 @@
         }
     }
 
+    // Note: All methods are run on main thread.
     class ManagerCallback implements MediaRouter2Manager.Callback {
 
         @Override
         public void onRoutesAdded(@NonNull List<MediaRoute2Info> routes) {
-            List<MediaRoute2Info> filteredRoutes =
-                    sManager.filterRoutesForPackage(routes, mClientPackageName);
+            updateAllRoutesFromManager();
+
+            List<MediaRoute2Info> filteredRoutes;
+            synchronized (mLock) {
+                filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+            }
             if (filteredRoutes.isEmpty()) {
                 return;
             }
@@ -1739,8 +1886,12 @@
 
         @Override
         public void onRoutesRemoved(@NonNull List<MediaRoute2Info> routes) {
-            List<MediaRoute2Info> filteredRoutes =
-                    sManager.filterRoutesForPackage(routes, mClientPackageName);
+            updateAllRoutesFromManager();
+
+            List<MediaRoute2Info> filteredRoutes;
+            synchronized (mLock) {
+                filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+            }
             if (filteredRoutes.isEmpty()) {
                 return;
             }
@@ -1752,8 +1903,12 @@
 
         @Override
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {
-            List<MediaRoute2Info> filteredRoutes =
-                    sManager.filterRoutesForPackage(routes, mClientPackageName);
+            updateAllRoutesFromManager();
+
+            List<MediaRoute2Info> filteredRoutes;
+            synchronized (mLock) {
+                filteredRoutes = filterRoutes(routes, mDiscoveryPreference);
+            }
             if (filteredRoutes.isEmpty()) {
                 return;
             }
@@ -1764,31 +1919,98 @@
         }
 
         @Override
-        public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
-            // TODO: Call ControllerCallback.onControllerUpdated
-        }
-
-        @Override
         public void onTransferred(@NonNull RoutingSessionInfo oldSession,
-                @Nullable RoutingSessionInfo newSession) {
-            // TODO: Call TransferCallback.onTransfer
+                @NonNull RoutingSessionInfo newSession) {
+            if (!oldSession.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, oldSession.getClientPackageName())) {
+                return;
+            }
+
+            if (!newSession.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, newSession.getClientPackageName())) {
+                return;
+            }
+
+            // For successful in-session transfer, onControllerUpdated() handles it.
+            if (TextUtils.equals(oldSession.getId(), newSession.getId())) {
+                return;
+            }
+
+
+            RoutingController oldController;
+            if (oldSession.isSystemSession()) {
+                mSystemController.setRoutingSessionInfo(oldSession);
+                oldController = mSystemController;
+            } else {
+                oldController = new RoutingController(oldSession);
+            }
+
+            RoutingController newController;
+            if (oldSession.isSystemSession()) {
+                mSystemController.setRoutingSessionInfo(newSession);
+                newController = mSystemController;
+            } else {
+                newController = new RoutingController(newSession);
+            }
+
+            notifyTransfer(oldController, newController);
         }
 
         @Override
         public void onTransferFailed(@NonNull RoutingSessionInfo session,
                 @NonNull MediaRoute2Info route) {
-            // TODO: Call TransferCallback.onTransferFailure
+            if (!session.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                return;
+            }
+            notifyTransferFailure(route);
+        }
+
+        @Override
+        public void onSessionUpdated(@NonNull RoutingSessionInfo session) {
+            if (!session.isSystemSession()
+                    && !TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                return;
+            }
+
+            RoutingController controller;
+            if (session.isSystemSession()) {
+                mSystemController.setRoutingSessionInfo(session);
+                controller = mSystemController;
+            } else {
+                controller = new RoutingController(session);
+            }
+            notifyControllerUpdated(controller);
         }
 
         @Override
         public void onSessionReleased(@NonNull RoutingSessionInfo session) {
-            // TODO: Call TransferCallback.onStop()
+            if (session.isSystemSession()) {
+                Log.e(TAG, "onSessionReleased: Called on system session. Ignoring.");
+                return;
+            }
+
+            if (!TextUtils.equals(mClientPackageName, session.getClientPackageName())) {
+                return;
+            }
+
+            notifyStop(new RoutingController(session, RoutingController.CONTROLLER_STATE_RELEASED));
         }
 
         @Override
         public void onPreferredFeaturesChanged(@NonNull String packageName,
                 @NonNull List<String> preferredFeatures) {
-            // Does nothing.
+            if (!TextUtils.equals(mClientPackageName, packageName)) {
+                return;
+            }
+
+            synchronized (mLock) {
+                mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+                        preferredFeatures, true).build();
+            }
+
+            updateAllRoutesFromManager();
+            notifyPreferredFeaturesChanged(preferredFeatures);
         }
 
         @Override
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index ca619d4..20e3573 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -148,7 +148,7 @@
 
     /**
      * Starts scanning remote routes.
-     * @see #stopScan(String)
+     * @see #stopScan()
      */
     public void startScan() {
         Client client = getOrCreateClient();
@@ -163,7 +163,7 @@
 
     /**
      * Stops scanning remote routes to reduce resource consumption.
-     * @see #startScan(String)
+     * @see #startScan()
      */
     public void stopScan() {
         Client client = getOrCreateClient();
@@ -788,8 +788,8 @@
      * Requests releasing a session.
      * <p>
      * If a session is released, any operation on the session will be ignored.
-     * {@link Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)} with {@code null}
-     * session will be called when the session is released.
+     * {@link Callback#onSessionReleased(RoutingSessionInfo)} will be called
+     * when the session is released.
      * </p>
      *
      * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
@@ -945,10 +945,10 @@
          * Called when media is transferred.
          *
          * @param oldSession the previous session
-         * @param newSession the new session or {@code null} if the session is released.
+         * @param newSession the new session
          */
         default void onTransferred(@NonNull RoutingSessionInfo oldSession,
-                @Nullable RoutingSessionInfo newSession) { }
+                @NonNull RoutingSessionInfo newSession) { }
 
         /**
          * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 4b33366..a045714 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -8,9 +8,13 @@
   public class ConnectivityManager {
     method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List<android.net.NetworkStateSnapshot> getAllNetworkStateSnapshot();
     method @NonNull public static android.util.Range<java.lang.Integer> getIpSecNetIdRange();
+    method @NonNull public static String getPrivateDnsMode(@NonNull android.content.ContentResolver);
     method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
+    field public static final String PRIVATE_DNS_MODE_OFF = "off";
+    field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+    field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
   }
 
   public final class NetworkAgentConfig implements android.os.Parcelable {
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 7189be1..66c45ed 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -23,6 +23,8 @@
 import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
 import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
 import static android.net.QosCallback.QosCallbackRegistrationException;
+import static android.provider.Settings.Global.PRIVATE_DNS_DEFAULT_MODE;
+import static android.provider.Settings.Global.PRIVATE_DNS_MODE;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -31,11 +33,13 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -63,6 +67,7 @@
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Range;
@@ -802,24 +807,27 @@
     /**
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public static final String PRIVATE_DNS_MODE_OFF = "off";
     /**
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
     /**
      * @hide
      */
+    @SystemApi(client = MODULE_LIBRARIES)
     public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
-    /**
-     * The default Private DNS mode.
-     *
-     * This may change from release to release or may become dependent upon
-     * the capabilities of the underlying platform.
-     *
-     * @hide
-     */
-    public static final String PRIVATE_DNS_DEFAULT_MODE_FALLBACK = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef(value = {
+            PRIVATE_DNS_MODE_OFF,
+            PRIVATE_DNS_MODE_OPPORTUNISTIC,
+            PRIVATE_DNS_MODE_PROVIDER_HOSTNAME,
+    })
+    public @interface PrivateDnsMode {}
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
     private final IConnectivityManager mService;
@@ -5128,4 +5136,24 @@
     public static Range<Integer> getIpSecNetIdRange() {
         return new Range(TUN_INTF_NETID_START, TUN_INTF_NETID_START + TUN_INTF_NETID_RANGE - 1);
     }
+
+    /**
+     * Get private DNS mode from settings.
+     *
+     * @param cr The ContentResolver to query private DNS mode from settings.
+     * @return A string of private DNS mode as one of the PRIVATE_DNS_MODE_* constants.
+     *
+     * @hide
+     */
+    @SystemApi(client = MODULE_LIBRARIES)
+    @NonNull
+    @PrivateDnsMode
+    public static String getPrivateDnsMode(@NonNull ContentResolver cr) {
+        String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
+        if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
+        // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
+        // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
+        if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+        return mode;
+    }
 }
diff --git a/packages/Connectivity/framework/src/android/net/IpPrefix.java b/packages/Connectivity/framework/src/android/net/IpPrefix.java
index d2ee7d1..bf4481a 100644
--- a/packages/Connectivity/framework/src/android/net/IpPrefix.java
+++ b/packages/Connectivity/framework/src/android/net/IpPrefix.java
@@ -113,7 +113,7 @@
         // first statement in constructor". We could factor out setting the member variables to an
         // init() method, but if we did, then we'd have to make the members non-final, or "error:
         // cannot assign a value to final variable address". So we just duplicate the code here.
-        Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(prefix);
+        Pair<InetAddress, Integer> ipAndMask = NetworkUtils.legacyParseIpAndMask(prefix);
         this.address = ipAndMask.first.getAddress();
         this.prefixLength = ipAndMask.second;
         checkAndMaskAddressAndPrefixLength();
diff --git a/packages/Connectivity/framework/src/android/net/LinkAddress.java b/packages/Connectivity/framework/src/android/net/LinkAddress.java
index d1bdaa0..d48b8c7 100644
--- a/packages/Connectivity/framework/src/android/net/LinkAddress.java
+++ b/packages/Connectivity/framework/src/android/net/LinkAddress.java
@@ -325,7 +325,7 @@
     public LinkAddress(@NonNull String address, int flags, int scope) {
         // This may throw an IllegalArgumentException; catching it is the caller's responsibility.
         // TODO: consider rejecting mapped IPv4 addresses such as "::ffff:192.0.2.5/24".
-        Pair<InetAddress, Integer> ipAndMask = NetworkUtils.parseIpAndMask(address);
+        Pair<InetAddress, Integer> ipAndMask = NetworkUtils.legacyParseIpAndMask(address);
         init(ipAndMask.first, ipAndMask.second, flags, scope, LIFETIME_UNKNOWN, LIFETIME_UNKNOWN);
     }
 
diff --git a/packages/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index 9e42bbe..c0f2628 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -27,8 +27,10 @@
 import java.io.FileDescriptor;
 import java.math.BigInteger;
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.SocketException;
+import java.net.UnknownHostException;
 import java.util.Locale;
 import java.util.TreeSet;
 
@@ -212,7 +214,7 @@
     @Deprecated
     public static InetAddress numericToInetAddress(String addrString)
             throws IllegalArgumentException {
-        return InetAddress.parseNumericAddress(addrString);
+        return InetAddresses.parseNumericAddress(addrString);
     }
 
     /**
@@ -234,7 +236,7 @@
         try {
             String[] pieces = ipAndMaskString.split("/", 2);
             prefixLength = Integer.parseInt(pieces[1]);
-            address = InetAddress.parseNumericAddress(pieces[0]);
+            address = InetAddresses.parseNumericAddress(pieces[0]);
         } catch (NullPointerException e) {            // Null string.
         } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
         } catch (NumberFormatException e) {           // Non-numeric prefix.
@@ -249,6 +251,47 @@
     }
 
     /**
+     * Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64".
+     * @hide
+     *
+     * @deprecated This method is used only for IpPrefix and LinkAddress. Since Android S, use
+     *             {@link #parseIpAndMask(String)}, if possible.
+     */
+    @Deprecated
+    public static Pair<InetAddress, Integer> legacyParseIpAndMask(String ipAndMaskString) {
+        InetAddress address = null;
+        int prefixLength = -1;
+        try {
+            String[] pieces = ipAndMaskString.split("/", 2);
+            prefixLength = Integer.parseInt(pieces[1]);
+            if (pieces[0] == null || pieces[0].isEmpty()) {
+                final byte[] bytes = new byte[16];
+                bytes[15] = 1;
+                return new Pair<InetAddress, Integer>(Inet6Address.getByAddress(
+                        "ip6-localhost"/* host */, bytes, 0 /* scope_id */), prefixLength);
+            }
+
+            if (pieces[0].startsWith("[")
+                    && pieces[0].endsWith("]")
+                    && pieces[0].indexOf(':') != -1) {
+                pieces[0] = pieces[0].substring(1, pieces[0].length() - 1);
+            }
+            address = InetAddresses.parseNumericAddress(pieces[0]);
+        } catch (NullPointerException e) {            // Null string.
+        } catch (ArrayIndexOutOfBoundsException e) {  // No prefix length.
+        } catch (NumberFormatException e) {           // Non-numeric prefix.
+        } catch (IllegalArgumentException e) {        // Invalid IP address.
+        } catch (UnknownHostException e) {            // IP address length is illegal
+        }
+
+        if (address == null || prefixLength == -1) {
+            throw new IllegalArgumentException("Invalid IP address and mask " + ipAndMaskString);
+        }
+
+        return new Pair<InetAddress, Integer>(address, prefixLength);
+    }
+
+    /**
      * Convert a 32 char hex string into a Inet6Address.
      * throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
      * made into an Inet6Address
diff --git a/packages/Connectivity/framework/src/android/net/ProxyInfo.java b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
index 229db0d..745e20f 100644
--- a/packages/Connectivity/framework/src/android/net/ProxyInfo.java
+++ b/packages/Connectivity/framework/src/android/net/ProxyInfo.java
@@ -129,7 +129,7 @@
     }
 
     /**
-     * Only used in PacProxyInstaller after Local Proxy is bound.
+     * Only used in PacProxyService after Local Proxy is bound.
      * @hide
      */
     public ProxyInfo(@NonNull Uri pacFileUrl, int localProxyPort) {
diff --git a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
index 340141b..c510079 100644
--- a/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
+++ b/packages/Connectivity/framework/src/android/net/VpnTransportInfo.java
@@ -42,9 +42,9 @@
             MessageUtils.findMessageNames(new Class[]{VpnManager.class}, new String[]{"TYPE_VPN_"});
 
     /** Type of this VPN. */
-    @VpnManager.VpnType public final int type;
+    public final int type;
 
-    public VpnTransportInfo(@VpnManager.VpnType int type) {
+    public VpnTransportInfo(int type) {
         this.type = type;
     }
 
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
index 781bfcd..1ccf417 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppSwitchPreference.java
@@ -51,6 +51,7 @@
 
     @Override
     public void onBindViewHolder(PreferenceViewHolder holder) {
+        setSingleLineTitle(true);
         super.onBindViewHolder(holder);
         final View switchView = holder.findViewById(android.R.id.switch_widget);
         if (switchView != null) {
diff --git a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
index 541a246..281269ec 100644
--- a/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
+++ b/packages/SettingsLib/HelpUtils/src/com/android/settingslib/HelpUtils.java
@@ -223,7 +223,7 @@
      *
      * @return the uri with added query parameters
      */
-    private static Uri uriWithAddedParameters(Context context, Uri baseUri) {
+    public static Uri uriWithAddedParameters(Context context, Uri baseUri) {
         Uri.Builder builder = baseUri.buildUpon();
 
         // Add in the preferred language
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 8fd1910..129aca4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -386,7 +386,6 @@
                 case BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT:
                 case BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS:
                 case BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED:
-                case BluetoothDevice.UNBOND_REASON_REMOVED:
                     errorMsg = R.string.bluetooth_pairing_error_message;
                     break;
                 default:
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
index e7a20b3..c88ed8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/OWNERS
@@ -1,6 +1,7 @@
 # Default reviewers for this and subdirectories.
 andychou@google.com
 arcwang@google.com
+changbetty@google.com
 goldmanj@google.com
 qal@google.com
 wengsu@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
index 0bde5c0..f3b600c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS
@@ -2,6 +2,7 @@
 andychou@google.com
 arcwang@google.com
 asapperstein@google.com
+changbetty@google.com
 goldmanj@google.com
 qal@google.com
 wengsu@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
index 6a4d650e..4bff78f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothEventManagerTest.java
@@ -353,21 +353,6 @@
     }
 
     @Test
-    public void showUnbondMessage_reasonRemoved_showCorrectedErrorCode() {
-        mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
-        mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
-        mIntent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
-        mIntent.putExtra(BluetoothDevice.EXTRA_REASON, BluetoothDevice.UNBOND_REASON_REMOVED);
-        when(mCachedDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedDevice1);
-        when(mCachedDevice1.getName()).thenReturn(DEVICE_NAME);
-
-        mContext.sendBroadcast(mIntent);
-
-        verify(mErrorListener).onShowError(any(Context.class), eq(DEVICE_NAME),
-                eq(R.string.bluetooth_pairing_error_message));
-    }
-
-    @Test
     public void showUnbondMessage_reasonAuthTimeout_showCorrectedErrorCode() {
         mIntent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
         mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index e8e10a4..db9b83e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -281,5 +281,6 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
                 new InclusiveFloatRangeValidator(0.0f, 1.0f));
         VALIDATORS.put(Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index a0b9528..7288371 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1990,6 +1990,13 @@
         dumpSetting(s, p,
                 Settings.Secure.CARRIER_APPS_HANDLED,
                 SecureSettingsProto.CARRIER_APPS_HANDLED);
+
+        final long clipboardToken = p.start(SecureSettingsProto.CLIPBOARD);
+        dumpSetting(s, p,
+                Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
+                SecureSettingsProto.Clipboard.SHOW_ACCESS_NOTIFICATIONS);
+        p.end(clipboardToken);
+
         dumpSetting(s, p,
                 Settings.Secure.CMAS_ADDITIONAL_BROADCAST_PKG,
                 SecureSettingsProto.CMAS_ADDITIONAL_BROADCAST_PKG);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b6fd286..6574353 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -54,7 +54,7 @@
     name: "SystemUI-sensors",
     srcs: [
         "src/com/android/systemui/util/sensors/ThresholdSensor.java",
-    ]
+    ],
 }
 
 android_library {
@@ -99,7 +99,7 @@
         "SystemUI-tags",
         "SystemUI-proto",
         "dagger2",
-        "jsr330"
+        "jsr330",
     ],
     manifest: "AndroidManifest.xml",
 
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 9e67e4b..d6204db 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -25,7 +25,10 @@
 
     name: "SystemUIPluginLib",
 
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "bcsmartspace/src/**/*.java",
+    ],
 
     static_libs: [
         "PluginCoreLib",
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
new file mode 100644
index 0000000..f8a9a045
--- /dev/null
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2021 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.plugins;
+
+import android.os.Parcelable;
+
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
+import java.util.List;
+
+/**
+ * Interface to provide SmartspaceTargets to BcSmartspace.
+ */
+@ProvidesInterface(action = BcSmartspaceDataPlugin.ACTION, version = BcSmartspaceDataPlugin.VERSION)
+public interface BcSmartspaceDataPlugin extends Plugin {
+    String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
+    int VERSION = 1;
+
+    /** Register a listener to get Smartspace data. */
+    void registerListener(SmartspaceTargetListener listener);
+
+    /** Unregister a listener. */
+    void unregisterListener(SmartspaceTargetListener listener);
+
+    /** Provides Smartspace data to registered listeners. */
+    interface SmartspaceTargetListener {
+        /** Each Parcelable is a SmartspaceTarget that represents a card. */
+        void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets);
+    }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 70be7c6..a8083f1 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -851,8 +851,6 @@
     <string name="quick_settings_wifi_label">Wi-Fi</string>
     <!-- QuickSettings: Internet [CHAR LIMIT=NONE] -->
     <string name="quick_settings_internet_label">Internet</string>
-    <!-- QuickSettings: Airplane-safe [CHAR LIMIT=NONE] -->
-    <string name="quick_settings_airplane_safe_label">Airplane-safe</string>
     <!-- QuickSettings: networks available [CHAR LIMIT=NONE] -->
     <string name="quick_settings_networks_available">Networks available</string>
     <!-- QuickSettings: networks unavailable [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index a580663..9f32c03 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -185,8 +185,8 @@
             if (timeoutMs == 0) {
                 mMessageAreaController.setMessage(mView.getWrongPasswordStringId());
             }
+            mView.resetPasswordText(true /* animate */, false /* announce deletion if no match */);
         }
-        mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
     }
 
     protected void verifyPasswordAndUnlock() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index de64f07..40190c1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -31,6 +31,7 @@
  * A Base class for all Keyguard password/pattern/pin related inputs.
  */
 public abstract class KeyguardInputView extends LinearLayout {
+    private Runnable mOnFinishImeAnimationRunnable;
 
     public KeyguardInputView(Context context) {
         super(context);
@@ -47,7 +48,8 @@
 
     abstract CharSequence getTitle();
 
-    void animateForIme(float interpolatedFraction) {
+    void animateForIme(float interpolatedFraction, boolean appearingAnim) {
+        return;
     }
 
     boolean disallowInterceptTouch(MotionEvent event) {
@@ -85,4 +87,14 @@
         };
     }
 
+    public void setOnFinishImeAnimationRunnable(Runnable onFinishImeAnimationRunnable) {
+        mOnFinishImeAnimationRunnable = onFinishImeAnimationRunnable;
+    }
+
+    public void runOnFinishImeAnimationRunnable() {
+        if (mOnFinishImeAnimationRunnable != null) {
+            mOnFinishImeAnimationRunnable.run();
+            mOnFinishImeAnimationRunnable = null;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 533bec1..cd9627f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.view.WindowInsets.Type.ime;
+
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
@@ -23,15 +25,25 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.widget.LockscreenCredential;
 import com.android.internal.widget.TextViewInputDisabler;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 /**
  * Displays an alphanumeric (latin-1) key entry for the user to enter
@@ -41,6 +53,8 @@
 
     private final int mDisappearYTranslation;
 
+    private static final long IME_DISAPPEAR_DURATION_MS = 125;
+
     // A delay constant to be used in a workaround for the situation where InputMethodManagerService
     // is not switched to the new user yet.
     // TODO: Remove this by ensuring such a race condition never happens.
@@ -150,9 +164,63 @@
     }
 
     @Override
-    public void animateForIme(float interpolatedFraction) {
+    public boolean startDisappearAnimation(Runnable finishRunnable) {
+        getWindowInsetsController().controlWindowInsetsAnimation(ime(),
+                100,
+                Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
+
+                    @Override
+                    public void onReady(@NonNull WindowInsetsAnimationController controller,
+                            int types) {
+                        ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
+                        anim.addUpdateListener(animation -> {
+                            if (controller.isCancelled()) {
+                                return;
+                            }
+                            Insets shownInsets = controller.getShownStateInsets();
+                            Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
+                                    (int) (-shownInsets.bottom / 4
+                                            * anim.getAnimatedFraction())));
+                            controller.setInsetsAndAlpha(insets,
+                                    (float) animation.getAnimatedValue(),
+                                    anim.getAnimatedFraction());
+                        });
+                        anim.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                            }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                controller.finish(false);
+                                runOnFinishImeAnimationRunnable();
+                                finishRunnable.run();
+                            }
+                        });
+                        anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+                        anim.start();
+                    }
+
+                    @Override
+                    public void onFinished(
+                            @NonNull WindowInsetsAnimationController controller) {
+                    }
+
+                    @Override
+                    public void onCancelled(
+                            @Nullable WindowInsetsAnimationController controller) {
+                    }
+                });
+        return true;
+    }
+
+
+    @Override
+    public void animateForIme(float interpolatedFraction, boolean appearingAnim) {
         animate().cancel();
-        setAlpha(Math.max(interpolatedFraction, getAlpha()));
+        setAlpha(appearingAnim
+                ? Math.max(interpolatedFraction, getAlpha())
+                : 1 - interpolatedFraction);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 57b8cf0..e45dd8b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -189,23 +189,22 @@
             return;
         }
         if (wasDisabled) {
-            mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
+            showInput();
         }
     }
 
     @Override
     public void onResume(int reason) {
         super.onResume(reason);
-
-        mPasswordEntry.requestFocus();
         if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
             showInput();
         }
     }
 
     private void showInput() {
-        mPasswordEntry.post(() -> {
-            if (mPasswordEntry.isFocused() && mView.isShown()) {
+        mView.post(() -> {
+            if (mView.isShown()) {
+                mPasswordEntry.requestFocus();
                 mInputMethodManager.showSoftInput(
                         mPasswordEntry, InputMethodManager.SHOW_IMPLICIT);
             }
@@ -214,7 +213,18 @@
 
     @Override
     public void onPause() {
-        super.onPause();
+        if (!mPasswordEntry.isVisibleToUser()) {
+            // Reset all states directly and then hide IME when the screen turned off.
+            super.onPause();
+        } else {
+            // In order not to break the IME hide animation by resetting states too early after
+            // the password checked, make sure resetting states after the IME hiding animation
+            // finished.
+            mView.setOnFinishImeAnimationRunnable(() -> {
+                mPasswordEntry.clearFocus();
+                super.onPause();
+            });
+        }
         mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0);
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index eaf8516..4887767 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -23,11 +23,9 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
-import android.graphics.Insets;
 import android.graphics.Rect;
 import android.provider.Settings;
 import android.util.AttributeSet;
@@ -42,12 +40,9 @@
 import android.view.ViewPropertyAnimator;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
-import android.view.WindowInsetsAnimationControlListener;
-import android.view.WindowInsetsAnimationController;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -130,6 +125,9 @@
                         WindowInsetsAnimation.Bounds bounds) {
                     if (!mDisappearAnimRunning) {
                         beginJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
+                    } else {
+                        beginJankInstrument(
+                                InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
                     }
                     mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
                     return bounds;
@@ -138,25 +136,28 @@
                 @Override
                 public WindowInsets onProgress(WindowInsets windowInsets,
                         List<WindowInsetsAnimation> list) {
-                    if (mDisappearAnimRunning) {
-                        mSecurityViewFlipper.setTranslationY(
-                                mInitialBounds.bottom - mFinalBounds.bottom);
-                    } else {
-                        int translationY = 0;
-                        float interpolatedFraction = 1f;
-                        for (WindowInsetsAnimation animation : list) {
-                            if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
-                                continue;
-                            }
-                            interpolatedFraction = animation.getInterpolatedFraction();
-
-                            final int paddingBottom = (int) MathUtils.lerp(
-                                    mInitialBounds.bottom - mFinalBounds.bottom, 0,
-                                    interpolatedFraction);
-                            translationY += paddingBottom;
+                    float start = mDisappearAnimRunning
+                            ? -(mFinalBounds.bottom - mInitialBounds.bottom)
+                            : mInitialBounds.bottom - mFinalBounds.bottom;
+                    float end = mDisappearAnimRunning
+                            ? -((mFinalBounds.bottom - mInitialBounds.bottom) * 0.75f)
+                            : 0f;
+                    int translationY = 0;
+                    float interpolatedFraction = 1f;
+                    for (WindowInsetsAnimation animation : list) {
+                        if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
+                            continue;
                         }
-                        mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction);
+                        interpolatedFraction = animation.getInterpolatedFraction();
+
+                        final int paddingBottom = (int) MathUtils.lerp(
+                                start, end,
+                                interpolatedFraction);
+                        translationY += paddingBottom;
                     }
+                    mSecurityViewFlipper.animateForIme(translationY, interpolatedFraction,
+                            !mDisappearAnimRunning);
+
                     return windowInsets;
                 }
 
@@ -164,7 +165,10 @@
                 public void onEnd(WindowInsetsAnimation animation) {
                     if (!mDisappearAnimRunning) {
                         endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_APPEAR);
-                        mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f);
+                        mSecurityViewFlipper.animateForIme(0, /* interpolatedFraction */ 1f,
+                                true /* appearingAnim */);
+                    } else {
+                        endJankInstrument(InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
                     }
                 }
             };
@@ -522,63 +526,6 @@
 
     public void startDisappearAnimation(SecurityMode securitySelection) {
         mDisappearAnimRunning = true;
-        if (securitySelection == SecurityMode.Password) {
-            mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
-                    IME_DISAPPEAR_DURATION_MS,
-                    Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
-
-
-                        @Override
-                        public void onReady(@NonNull WindowInsetsAnimationController controller,
-                                int types) {
-                            ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f);
-                            anim.addUpdateListener(animation -> {
-                                if (controller.isCancelled()) {
-                                    return;
-                                }
-                                Insets shownInsets = controller.getShownStateInsets();
-                                Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0,
-                                        (int) (-shownInsets.bottom / 4
-                                                * anim.getAnimatedFraction())));
-                                controller.setInsetsAndAlpha(insets,
-                                        (float) animation.getAnimatedValue(),
-                                        anim.getAnimatedFraction());
-                            });
-                            anim.addListener(new AnimatorListenerAdapter() {
-                                @Override
-                                public void onAnimationStart(Animator animation) {
-                                    beginJankInstrument(
-                                            InteractionJankMonitor
-                                                    .CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
-                                }
-
-                                @Override
-                                public void onAnimationEnd(Animator animation) {
-                                    endJankInstrument(
-                                            InteractionJankMonitor
-                                                    .CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
-                                    controller.finish(false);
-                                }
-                            });
-                            anim.setDuration(IME_DISAPPEAR_DURATION_MS);
-                            anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-                            anim.start();
-                        }
-
-                        @Override
-                        public void onFinished(
-                                @NonNull WindowInsetsAnimationController controller) {
-                            mDisappearAnimRunning = false;
-                        }
-
-                        @Override
-                        public void onCancelled(
-                                @Nullable WindowInsetsAnimationController controller) {
-                            cancelJankInstrument(
-                                    InteractionJankMonitor.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR);
-                        }
-                    });
-        }
     }
 
     private void beginJankInstrument(int cuj) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 75ef4b32..e01e17d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -87,10 +87,10 @@
       * Translate the entire view, and optionally inform the wrapped view of the progress
       * so it can animate with the parent.
       */
-    public void animateForIme(int translationY, float interpolatedFraction) {
+    public void animateForIme(int translationY, float interpolatedFraction, boolean appearingAnim) {
         super.setTranslationY(translationY);
         KeyguardInputView v = getSecurityView();
-        if (v != null) v.animateForIme(interpolatedFraction);
+        if (v != null) v.animateForIme(interpolatedFraction, appearingAnim);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index 97d6e97..14b9691 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -15,6 +15,7 @@
  */
 package com.android.keyguard;
 
+import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -34,7 +35,9 @@
  * Provides background color and radius animations for key pad buttons.
  */
 class NumPadAnimator {
-    private ValueAnimator mAnimator;
+    private AnimatorSet mAnimator;
+    private ValueAnimator mExpandAnimator;
+    private ValueAnimator mContractAnimator;
     private GradientDrawable mBackground;
     private RippleDrawable mRipple;
     private GradientDrawable mRippleMask;
@@ -55,18 +58,28 @@
         mMargin = context.getResources().getDimensionPixelSize(R.dimen.num_pad_key_margin);
 
         // Actual values will be updated later, usually during an onLayout() call
-        mAnimator = ValueAnimator.ofFloat(0f);
-        mAnimator.setDuration(100);
-        mAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-        mAnimator.setRepeatMode(ValueAnimator.REVERSE);
-        mAnimator.setRepeatCount(1);
-        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+        mAnimator = new AnimatorSet();
+        mExpandAnimator = ValueAnimator.ofFloat(0f, 1f);
+        mExpandAnimator.setDuration(50);
+        mExpandAnimator.setInterpolator(Interpolators.LINEAR);
+        mExpandAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 public void onAnimationUpdate(ValueAnimator anim) {
                     mBackground.setCornerRadius((float) anim.getAnimatedValue());
                     mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
                 }
         });
 
+        mContractAnimator = ValueAnimator.ofFloat(1f, 0f);
+        mContractAnimator.setStartDelay(33);
+        mContractAnimator.setDuration(417);
+        mContractAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mContractAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                public void onAnimationUpdate(ValueAnimator anim) {
+                    mBackground.setCornerRadius((float) anim.getAnimatedValue());
+                    mRippleMask.setCornerRadius((float) anim.getAnimatedValue());
+                }
+        });
+        mAnimator.playSequentially(mExpandAnimator, mContractAnimator);
     }
 
     void updateMargin(ViewGroup.MarginLayoutParams lp) {
@@ -77,7 +90,8 @@
         float startRadius = height / 2f;
         float endRadius = height / 4f;
         mBackground.setCornerRadius(startRadius);
-        mAnimator.setFloatValues(startRadius, endRadius);
+        mExpandAnimator.setFloatValues(startRadius, endRadius);
+        mContractAnimator.setFloatValues(endRadius, startRadius);
     }
 
     void start() {
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 5db3349..1635c49 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.VectorDrawable;
 import android.util.AttributeSet;
@@ -24,6 +25,8 @@
 import android.view.MotionEvent;
 import android.view.ViewGroup;
 
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
@@ -32,13 +35,19 @@
  */
 public class NumPadButton extends AlphaOptimizedImageButton {
 
+    @Nullable
     private NumPadAnimator mAnimator;
 
     public NumPadButton(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
-                attrs.getStyleAttribute());
+        Drawable background = getBackground();
+        if (background instanceof LayerDrawable) {
+            mAnimator = new NumPadAnimator(context, (LayerDrawable) background,
+                    attrs.getStyleAttribute());
+        } else {
+            mAnimator = null;
+        }
     }
 
     @Override
@@ -93,9 +102,11 @@
      * By default, the new layout will be enabled. Invoking will revert to the old style
      */
     public void disableNewLayout() {
-        mAnimator = null;
-        ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
-        setBackground(getContext().getResources().getDrawable(
-                R.drawable.ripple_drawable_pin, ctw.getTheme()));
+        if (mAnimator != null) {
+            mAnimator = null;
+            ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+            setBackground(getContext().getResources().getDrawable(
+                    R.drawable.ripple_drawable_pin, ctw.getTheme()));
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index e6a9d4f..96fceee 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -31,6 +32,8 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
@@ -48,6 +51,7 @@
     private int mTextViewResId;
     private PasswordTextView mTextView;
 
+    @Nullable
     private NumPadAnimator mAnimator;
 
     private View.OnClickListener mListener = new View.OnClickListener() {
@@ -127,8 +131,13 @@
 
         setContentDescription(mDigitText.getText().toString());
 
-        mAnimator = new NumPadAnimator(context, (LayerDrawable) getBackground(),
-                R.style.NumPadKey);
+        Drawable background = getBackground();
+        if (background instanceof LayerDrawable) {
+            mAnimator = new NumPadAnimator(context, (LayerDrawable) background,
+                    R.style.NumPadKey);
+        } else {
+            mAnimator = null;
+        }
     }
 
     /**
@@ -136,10 +145,12 @@
      */
     public void disableNewLayout() {
         findViewById(R.id.klondike_text).setVisibility(View.VISIBLE);
-        mAnimator = null;
-        ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
-        setBackground(getContext().getResources().getDrawable(
-                R.drawable.ripple_drawable_pin, ctw.getTheme()));
+        if (mAnimator != null) {
+            mAnimator = null;
+            ContextThemeWrapper ctw = new ContextThemeWrapper(getContext(), R.style.NumPadKey);
+            setBackground(getContext().getResources().getDrawable(
+                    R.drawable.ripple_drawable_pin, ctw.getTheme()));
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 5ffc283..b80f8bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -121,9 +121,19 @@
     public PasswordTextView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        setFocusableInTouchMode(true);
-        setFocusable(true);
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
+        TypedArray a = context.obtainStyledAttributes(attrs, android.R.styleable.View);
+        try {
+            // If defined, use the provided values. If not, set them to true by default.
+            boolean isFocusable = a.getBoolean(android.R.styleable.View_focusable,
+                    /* defValue= */ true);
+            boolean isFocusableInTouchMode = a.getBoolean(
+                    android.R.styleable.View_focusableInTouchMode, /* defValue= */ true);
+            setFocusable(isFocusable);
+            setFocusableInTouchMode(isFocusableInTouchMode);
+        } finally {
+            a.recycle();
+        }
+        a = context.obtainStyledAttributes(attrs, R.styleable.PasswordTextView);
         try {
             mTextHeightRaw = a.getInt(R.styleable.PasswordTextView_scaledTextSize, 0);
             mGravity = a.getInt(R.styleable.PasswordTextView_android_gravity, Gravity.CENTER);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 1bf7474..c051b69 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -53,12 +53,18 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.util.Assert;
 
 import java.util.Locale;
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+
 /**
  * Class to register system actions with accessibility framework.
  */
@@ -128,23 +134,39 @@
     public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT =
             AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_SHORTCUT; // 13
 
+    public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
+            AccessibilityService.GLOBAL_ACTION_DISMISS_NOTIFICATION_SHADE; // 15
+
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
-    private SystemActionsBroadcastReceiver mReceiver;
+    private final SystemActionsBroadcastReceiver mReceiver;
     private Locale mLocale;
-    private AccessibilityManager mA11yManager;
+    private final AccessibilityManager mA11yManager;
+    private final Lazy<StatusBar> mStatusBar;
+    private final NotificationShadeWindowController mNotificationShadeController;
+    private final StatusBarWindowCallback mNotificationShadeCallback;
+    private boolean mDismissNotificationShadeActionRegistered;
 
     @Inject
-    public SystemActions(Context context) {
+    public SystemActions(Context context,
+            NotificationShadeWindowController notificationShadeController,
+            Lazy<StatusBar> statusBar) {
         super(context);
         mReceiver = new SystemActionsBroadcastReceiver();
         mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
         mA11yManager = (AccessibilityManager) mContext.getSystemService(
                 Context.ACCESSIBILITY_SERVICE);
+        mNotificationShadeController = notificationShadeController;
+        // Saving in instance variable since to prevent GC since
+        // NotificationShadeWindowController.registerCallback() only keeps weak references.
+        mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing) ->
+                registerOrUnregisterDismissNotificationShadeAction();
+        mStatusBar = statusBar;
     }
 
     @Override
     public void start() {
+        mNotificationShadeController.registerCallback(mNotificationShadeCallback);
         mContext.registerReceiverForAllUsers(
                 mReceiver,
                 mReceiver.createIntentFilter(),
@@ -210,6 +232,32 @@
         mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
         mA11yManager.registerSystemAction(
                 actionAccessibilityShortcut, SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT);
+        registerOrUnregisterDismissNotificationShadeAction();
+    }
+
+    private void registerOrUnregisterDismissNotificationShadeAction() {
+        Assert.isMainThread();
+
+        // Saving state in instance variable since this callback is called quite often to avoid
+        // binder calls
+        StatusBar statusBar = mStatusBar.get();
+        if (statusBar.isPanelExpanded() && !statusBar.isKeyguardShowing()) {
+            if (!mDismissNotificationShadeActionRegistered) {
+                mA11yManager.registerSystemAction(
+                        createRemoteAction(
+                                R.string.accessibility_system_action_dismiss_notification_shade,
+                                SystemActionsBroadcastReceiver
+                                        .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE),
+                        SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+                mDismissNotificationShadeActionRegistered = true;
+            }
+        } else {
+            if (mDismissNotificationShadeActionRegistered) {
+                mA11yManager.unregisterSystemAction(
+                        SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
+                mDismissNotificationShadeActionRegistered = false;
+            }
+        }
     }
 
     /**
@@ -261,10 +309,15 @@
                         R.string.accessibility_system_action_on_screen_a11y_shortcut_chooser_label;
                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER;
                 break;
-            case  SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
+            case SYSTEM_ACTION_ID_ACCESSIBILITY_SHORTCUT:
                 labelId = R.string.accessibility_system_action_hardware_a11y_shortcut_label;
                 intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_SHORTCUT;
                 break;
+            case SYSTEM_ACTION_ID_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE:
+                labelId = R.string.accessibility_system_action_dismiss_notification_shade;
+                intent = SystemActionsBroadcastReceiver
+                        .INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE;
+                break;
             default:
                 return;
         }
@@ -369,6 +422,10 @@
         mA11yManager.performAccessibilityShortcut();
     }
 
+    private void handleAccessibilityDismissNotificationShade() {
+        mStatusBar.get().animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, false /* force */);
+    }
+
     private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
         private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
         private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -384,6 +441,8 @@
                 "SYSTEM_ACTION_ACCESSIBILITY_BUTTON_MENU";
         private static final String INTENT_ACTION_ACCESSIBILITY_SHORTCUT =
                 "SYSTEM_ACTION_ACCESSIBILITY_SHORTCUT";
+        private static final String INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE =
+                "SYSTEM_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE";
 
         private PendingIntent createPendingIntent(Context context, String intentAction) {
             switch (intentAction) {
@@ -397,7 +456,8 @@
                 case INTENT_ACTION_TAKE_SCREENSHOT:
                 case INTENT_ACTION_ACCESSIBILITY_BUTTON:
                 case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER:
-                case INTENT_ACTION_ACCESSIBILITY_SHORTCUT: {
+                case INTENT_ACTION_ACCESSIBILITY_SHORTCUT:
+                case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
                     Intent intent = new Intent(intentAction);
                     intent.setPackage(context.getPackageName());
                     return PendingIntent.getBroadcast(context, 0, intent,
@@ -422,6 +482,7 @@
             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
             intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_SHORTCUT);
+            intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE);
             return intentFilter;
         }
 
@@ -473,6 +534,10 @@
                     handleAccessibilityShortcut();
                     break;
                 }
+                case INTENT_ACTION_ACCESSIBILITY_DISMISS_NOTIFICATION_SHADE: {
+                    handleAccessibilityDismissNotificationShade();
+                    break;
+                }
                 default:
                     break;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 45cefcc..efb7992 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -31,7 +31,6 @@
 import com.android.systemui.dagger.qualifiers.TestHarness;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.sensors.ThresholdSensor;
 
 import java.io.FileDescriptor;
@@ -68,7 +67,6 @@
     private final SingleTapClassifier mSingleTapClassifier;
     private final DoubleTapClassifier mDoubleTapClassifier;
     private final HistoryTracker mHistoryTracker;
-    private final KeyguardStateController mKeyguardStateController;
     private final boolean mTestHarness;
     private final MetricsLogger mMetricsLogger;
     private int mIsFalseTouchCalls;
@@ -115,8 +113,7 @@
             DockManager dockManager, MetricsLogger metricsLogger,
             @Named(BRIGHT_LINE_GESTURE_CLASSIFERS) Set<FalsingClassifier> classifiers,
             SingleTapClassifier singleTapClassifier, DoubleTapClassifier doubleTapClassifier,
-            HistoryTracker historyTracker, KeyguardStateController keyguardStateController,
-            @TestHarness boolean testHarness) {
+            HistoryTracker historyTracker, @TestHarness boolean testHarness) {
         mDataProvider = falsingDataProvider;
         mDockManager = dockManager;
         mMetricsLogger = metricsLogger;
@@ -124,7 +121,6 @@
         mSingleTapClassifier = singleTapClassifier;
         mDoubleTapClassifier = doubleTapClassifier;
         mHistoryTracker = historyTracker;
-        mKeyguardStateController = keyguardStateController;
         mTestHarness = testHarness;
 
         mDataProvider.addSessionListener(mSessionListener);
@@ -138,10 +134,6 @@
 
     @Override
     public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
-        if (skipFalsing()) {
-            return false;
-        }
-
         boolean result;
 
         mDataProvider.setInteractionType(interactionType);
@@ -203,10 +195,6 @@
 
     @Override
     public boolean isFalseTap(boolean robustCheck, double falsePenalty) {
-        if (skipFalsing()) {
-            return false;
-        }
-
         FalsingClassifier.Result singleTapResult =
                 mSingleTapClassifier.isTap(mDataProvider.getRecentMotionEvents());
         mPriorResults = Collections.singleton(singleTapResult);
@@ -245,10 +233,6 @@
 
     @Override
     public boolean isFalseDoubleTap() {
-        if (skipFalsing()) {
-            return false;
-        }
-
         FalsingClassifier.Result result = mDoubleTapClassifier.classifyGesture();
         mPriorResults = Collections.singleton(result);
         if (result.isFalse()) {
@@ -262,10 +246,6 @@
         return result.isFalse();
     }
 
-    private boolean skipFalsing() {
-        return !mKeyguardStateController.isShowing();
-    }
-
     @Override
     public void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) {
         // TODO: some of these classifiers might allow us to abort early, meaning we don't have to
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index b359860..e090006 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -27,7 +27,6 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
 import com.android.systemui.util.time.SystemClock;
@@ -49,7 +48,6 @@
     private final HistoryTracker mHistoryTracker;
     private final ProximitySensor mProximitySensor;
     private final StatusBarStateController mStatusBarStateController;
-    private final KeyguardStateController mKeyguardStateController;
     private final SystemClock mSystemClock;
 
     private int mState;
@@ -89,14 +87,13 @@
     FalsingCollectorImpl(FalsingDataProvider falsingDataProvider, FalsingManager falsingManager,
             KeyguardUpdateMonitor keyguardUpdateMonitor, HistoryTracker historyTracker,
             ProximitySensor proximitySensor, StatusBarStateController statusBarStateController,
-            KeyguardStateController keyguardStateController, SystemClock systemClock) {
+            SystemClock systemClock) {
         mFalsingDataProvider = falsingDataProvider;
         mFalsingManager = falsingManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mHistoryTracker = historyTracker;
         mProximitySensor = proximitySensor;
         mStatusBarStateController = statusBarStateController;
-        mKeyguardStateController = keyguardStateController;
         mSystemClock = systemClock;
 
 
@@ -258,10 +255,6 @@
 
     @Override
     public void onTouchEvent(MotionEvent ev) {
-        if (!mKeyguardStateController.isShowing()) {
-            avoidGesture();
-            return;
-        }
         // We delay processing down events to see if another component wants to process them.
         // If #avoidGesture is called after a MotionEvent.ACTION_DOWN, all following motion events
         // will be ignored by the collector until another MotionEvent.ACTION_DOWN is passed in.
@@ -283,8 +276,8 @@
 
     @Override
     public void avoidGesture() {
-        mAvoidGesture = true;
         if (mPendingDownEvent != null) {
+            mAvoidGesture = true;
             mPendingDownEvent.recycle();
             mPendingDownEvent = null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
new file mode 100644
index 0000000..3bfdcae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.controls
+
+import android.service.controls.DeviceTypes.DeviceType
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.controls.ui.ControlViewHolder
+
+/**
+ * Interface for logging UI events related to controls
+ */
+interface ControlsMetricsLogger {
+
+    /**
+     * Assign a new instance id for this controls session, defined as when the controls area is
+     * made visible to when it is closed.
+     */
+    fun assignInstanceId()
+
+    fun touch(cvh: ControlViewHolder, isLocked: Boolean) {
+        log(ControlsEvents.CONTROL_TOUCH.id, cvh.deviceType, cvh.uid, isLocked)
+    }
+
+    fun drag(cvh: ControlViewHolder, isLocked: Boolean) {
+        log(ControlsEvents.CONTROL_DRAG.id, cvh.deviceType, cvh.uid, isLocked)
+    }
+
+    fun longPress(cvh: ControlViewHolder, isLocked: Boolean) {
+        log(ControlsEvents.CONTROL_LONG_PRESS.id, cvh.deviceType, cvh.uid, isLocked)
+    }
+
+    fun refreshBegin(uid: Int, isLocked: Boolean) {
+        assignInstanceId()
+        log(ControlsEvents.CONTROL_REFRESH_BEGIN.id, 0, uid, isLocked)
+    }
+
+    fun refreshEnd(cvh: ControlViewHolder, isLocked: Boolean) {
+        log(ControlsEvents.CONTROL_REFRESH_END.id, cvh.deviceType, cvh.uid, isLocked)
+    }
+
+    /**
+     * Logs a controls-related event
+     *
+     * @param eventId Main UIEvent to capture
+     * @param deviceType One of {@link android.service.controls.DeviceTypes}
+     * @param packageName Package name of the service that receives the request
+     * @param isLocked Is the device locked at the start of the action?
+     */
+    fun log(
+        eventId: Int,
+        @DeviceType deviceType: Int,
+        uid: Int,
+        isLocked: Boolean
+    )
+
+    private enum class ControlsEvents(val metricId: Int) : UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "User touched a control")
+        CONTROL_TOUCH(714),
+
+        @UiEvent(doc = "User dragged a control")
+        CONTROL_DRAG(713),
+
+        @UiEvent(doc = "User long-pressed a control")
+        CONTROL_LONG_PRESS(715),
+
+        @UiEvent(doc = "User has opened controls, and a state refresh has begun")
+        CONTROL_REFRESH_BEGIN(716),
+
+        @UiEvent(doc = "User has opened controls, and a state refresh has ended")
+        CONTROL_REFRESH_END(717);
+
+        override fun getId() = metricId
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
new file mode 100644
index 0000000..c165632
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2021 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.controls
+
+import android.service.controls.DeviceTypes.DeviceType
+
+import com.android.internal.logging.InstanceIdSequence
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.SysUiStatsLog
+
+import javax.inject.Inject
+
+/**
+ * Implementation for logging UI events related to controls
+ */
+@SysUISingleton
+class ControlsMetricsLoggerImpl @Inject constructor() : ControlsMetricsLogger {
+
+    companion object {
+        private const val INSTANCE_ID_MAX = 1 shl 13
+    }
+
+    private val instanceIdSequence = InstanceIdSequence(INSTANCE_ID_MAX)
+    private var instanceId = 0
+
+    override fun assignInstanceId() {
+        instanceId = instanceIdSequence.newInstanceId().id
+    }
+
+    /**
+     * {@see ControlsMetricsLogger#log}
+     */
+    override fun log(
+        eventId: Int,
+        @DeviceType deviceType: Int,
+        uid: Int,
+        isLocked: Boolean
+    ) {
+        SysUiStatsLog.write(
+            SysUiStatsLog.DEVICE_CONTROL_CHANGED,
+            eventId,
+            instanceId,
+            deviceType,
+            uid,
+            isLocked
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index ed625de..a165bb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -18,6 +18,8 @@
 
 import android.app.Activity
 import android.content.pm.PackageManager
+import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsMetricsLoggerImpl
 import com.android.systemui.controls.controller.ControlsBindingController
 import com.android.systemui.controls.controller.ControlsBindingControllerImpl
 import com.android.systemui.controls.controller.ControlsController
@@ -80,6 +82,9 @@
     abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
 
     @Binds
+    abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
+
+    @Binds
     abstract fun provideControlActionCoordinator(
         coordinator: ControlActionCoordinatorImpl
     ): ControlActionCoordinator
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 58a5981..477c220 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -18,7 +18,6 @@
 
 import android.annotation.MainThread
 import android.app.Dialog
-import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
 import android.content.pm.PackageManager
@@ -33,6 +32,7 @@
 import android.view.HapticFeedbackConstants
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsComponent
@@ -54,13 +54,15 @@
     private val globalActionsComponent: GlobalActionsComponent,
     private val taskViewFactory: Optional<TaskViewFactory>,
     private val broadcastDispatcher: BroadcastDispatcher,
-    private val lazyUiController: Lazy<ControlsUiController>
+    private val lazyUiController: Lazy<ControlsUiController>,
+    private val controlsMetricsLogger: ControlsMetricsLogger
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
     private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
     private var pendingAction: Action? = null
     private var actionsInProgress = mutableSetOf<String>()
-
+    private val isLocked: Boolean
+        get() = !keyguardStateController.isUnlocked()
     override var activityContext: Context? = null
 
     companion object {
@@ -73,6 +75,7 @@
     }
 
     override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
+        controlsMetricsLogger.touch(cvh, isLocked)
         bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
             cvh.action(BooleanAction(templateId, !isChecked))
@@ -80,6 +83,7 @@
     }
 
     override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
+        controlsMetricsLogger.touch(cvh, isLocked)
         val blockable = cvh.usePanel()
         bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK)
@@ -100,12 +104,14 @@
     }
 
     override fun setValue(cvh: ControlViewHolder, templateId: String, newValue: Float) {
+        controlsMetricsLogger.drag(cvh, isLocked)
         bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             cvh.action(FloatAction(templateId, newValue))
         }, false /* blockable */))
     }
 
     override fun longPress(cvh: ControlViewHolder) {
+        controlsMetricsLogger.longPress(cvh, isLocked)
         bouncerOrRun(createAction(cvh.cws.ci.controlId, {
             // Long press snould only be called when there is valid control state, otherwise ignore
             cvh.cws.control?.let {
@@ -116,7 +122,7 @@
     }
 
     override fun runPendingAction(controlId: String) {
-        if (!keyguardStateController.isUnlocked()) return
+        if (isLocked) return
         if (pendingAction?.controlId == controlId) {
             pendingAction?.invoke()
             pendingAction = null
@@ -141,28 +147,17 @@
     @VisibleForTesting
     fun bouncerOrRun(action: Action) {
         if (keyguardStateController.isShowing()) {
-            var closeDialog = !keyguardStateController.isUnlocked()
-            if (closeDialog) {
+            if (isLocked) {
                 context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
 
                 // pending actions will only run after the control state has been refreshed
                 pendingAction = action
             }
-
+            val wasLocked = isLocked
             activityStarter.dismissKeyguardThenExecute({
                 Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
-                if (closeDialog) {
-                    activityContext?.let {
-                        val i = Intent().apply {
-                            component = ComponentName(context, ControlsActivity::class.java)
-                            addFlags(
-                                Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
-                            putExtra(ControlsUiController.BACK_TO_GLOBAL_ACTIONS, false)
-                        }
-                        it.startActivity(i)
-                    } ?: run {
-                        globalActionsComponent.handleShowGlobalActionsMenu()
-                    }
+                if (wasLocked && activityContext == null) {
+                    globalActionsComponent.handleShowGlobalActionsMenu()
                 } else {
                     action.invoke()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 9d92a40..3e02890 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -49,6 +49,7 @@
 import com.android.internal.graphics.ColorUtils
 import com.android.systemui.Interpolators
 import com.android.systemui.R
+import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import kotlin.reflect.KClass
@@ -63,7 +64,9 @@
     val controlsController: ControlsController,
     val uiExecutor: DelayableExecutor,
     val bgExecutor: DelayableExecutor,
-    val controlActionCoordinator: ControlActionCoordinator
+    val controlActionCoordinator: ControlActionCoordinator,
+    val controlsMetricsLogger: ControlsMetricsLogger,
+    val uid: Int
 ) {
 
     companion object {
@@ -141,7 +144,7 @@
         status.setSelected(true)
     }
 
-    fun bindData(cws: ControlWithState) {
+    fun bindData(cws: ControlWithState, isLocked: Boolean) {
         // If an interaction is in progress, the update may visually interfere with the action the
         // action the user wants to make. Don't apply the update, and instead assume a new update
         // will coming from when the user interaction is complete.
@@ -171,10 +174,16 @@
             controlActionCoordinator.runPendingAction(cws.ci.controlId)
         }
 
+        val wasLoading = isLoading
         isLoading = false
         behavior = bindBehavior(behavior,
             findBehaviorClass(controlStatus, controlTemplate, deviceType))
         updateContentDescription()
+
+        // Only log one event per control, at the moment we have determined that the control
+        // switched from the loading to done state
+        val doneLoading = wasLoading && !isLoading
+        if (doneLoading) controlsMetricsLogger.refreshEnd(this, isLocked)
     }
 
     fun actionResponse(@ControlAction.ResponseResult response: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 0f7f48f..d08882b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -43,6 +43,7 @@
 import android.widget.Space
 import android.widget.TextView
 import com.android.systemui.R
+import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlInfo
@@ -58,6 +59,7 @@
 import com.android.systemui.globalactions.GlobalActionsPopupMenu
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.phone.ShadeController
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
 import java.text.Collator
@@ -77,7 +79,9 @@
     val controlActionCoordinator: ControlActionCoordinator,
     private val activityStarter: ActivityStarter,
     private val shadeController: ShadeController,
-    private val iconCache: CustomIconCache
+    private val iconCache: CustomIconCache,
+    private val controlsMetricsLogger: ControlsMetricsLogger,
+    private val keyguardStateController: KeyguardStateController
 ) : ControlsUiController {
 
     companion object {
@@ -133,7 +137,8 @@
         return object : ControlsListingController.ControlsListingCallback {
             override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
                 val lastItems = serviceInfos.map {
-                    SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName)
+                    val uid = it.serviceInfo.applicationInfo.uid
+                    SelectionItem(it.loadLabel(), "", it.loadIcon(), it.componentName, uid)
                 }
                 uiExecutor.execute {
                     parent.removeAllViews()
@@ -282,8 +287,19 @@
     private fun showControlsView(items: List<SelectionItem>) {
         controlViewsById.clear()
 
-        createListView()
-        createDropDown(items)
+        val itemsByComponent = items.associateBy { it.componentName }
+        val itemsWithStructure = mutableListOf<SelectionItem>()
+        allStructures.mapNotNullTo(itemsWithStructure) {
+            itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
+        }
+        itemsWithStructure.sortWith(localeComparator)
+
+        val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
+
+        controlsMetricsLogger.refreshBegin(selectionItem.uid, !keyguardStateController.isUnlocked())
+
+        createListView(selectionItem)
+        createDropDown(itemsWithStructure, selectionItem)
         createMenu()
     }
 
@@ -325,22 +341,13 @@
         })
     }
 
-    private fun createDropDown(items: List<SelectionItem>) {
+    private fun createDropDown(items: List<SelectionItem>, selected: SelectionItem) {
         items.forEach {
             RenderInfo.registerComponentIcon(it.componentName, it.icon)
         }
 
-        val itemsByComponent = items.associateBy { it.componentName }
-        val itemsWithStructure = mutableListOf<SelectionItem>()
-        allStructures.mapNotNullTo(itemsWithStructure) {
-            itemsByComponent.get(it.componentName)?.copy(structure = it.structure)
-        }
-        itemsWithStructure.sortWith(localeComparator)
-
-        val selectionItem = findSelectionItem(selectedStructure, itemsWithStructure) ?: items[0]
-
         var adapter = ItemAdapter(context, R.layout.controls_spinner_item).apply {
-            addAll(itemsWithStructure)
+            addAll(items)
         }
 
         /*
@@ -349,13 +356,13 @@
          * a similar effect
          */
         val spinner = parent.requireViewById<TextView>(R.id.app_or_structure_spinner).apply {
-            setText(selectionItem.getTitle())
+            setText(selected.getTitle())
             // override the default color on the dropdown drawable
             (getBackground() as LayerDrawable).getDrawable(0)
                 .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
         }
 
-        if (itemsWithStructure.size == 1) {
+        if (items.size == 1) {
             spinner.setBackground(null)
             return
         }
@@ -388,7 +395,7 @@
         })
     }
 
-    private fun createListView() {
+    private fun createListView(selected: SelectionItem) {
         val inflater = LayoutInflater.from(context)
         inflater.inflate(R.layout.controls_with_favorites, parent, true)
 
@@ -421,9 +428,11 @@
                     controlsController.get(),
                     uiExecutor,
                     bgExecutor,
-                    controlActionCoordinator
+                    controlActionCoordinator,
+                    controlsMetricsLogger,
+                    selected.uid
                 )
-                cvh.bindData(it)
+                cvh.bindData(it, false /* isLocked, will be ignored on initial load */)
                 controlViewsById.put(key, cvh)
             }
         }
@@ -528,6 +537,7 @@
     }
 
     override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+        val isLocked = !keyguardStateController.isUnlocked()
         controls.forEach { c ->
             controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
                 Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
@@ -536,8 +546,8 @@
                 val key = ControlKey(componentName, c.getControlId())
                 controlsById.put(key, cws)
 
-                uiExecutor.execute {
-                    controlViewsById.get(key)?.bindData(cws)
+                controlViewsById.get(key)?.let {
+                    uiExecutor.execute { it.bindData(cws, isLocked) }
                 }
             }
         }
@@ -566,7 +576,8 @@
     val appName: CharSequence,
     val structure: CharSequence,
     val icon: Drawable,
-    val componentName: ComponentName
+    val componentName: ComponentName,
+    val uid: Int
 ) {
     fun getTitle() = if (structure.isEmpty()) { appName } else { structure }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index baf35f2..45ecae0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -364,13 +364,6 @@
     /** */
     @Provides
     @SysUISingleton
-    public SystemActions providesSystemActions(Context context) {
-        return new SystemActions(context);
-    }
-
-    /** */
-    @Provides
-    @SysUISingleton
     public Choreographer providesChoreographer() {
         return Choreographer.getInstance();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 17f7ccf..97803c1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -149,9 +149,14 @@
                        RemoteAnimationTarget[] wallpapers,
                         RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) {
-            // TODO(bc-unlock): Calls KeyguardViewMediator#setOccluded to update the state and
-            // run animation.
             try {
+                if (transit == TRANSIT_OLD_KEYGUARD_OCCLUDE) {
+                    mBinder.setOccluded(true /* isOccluded */, true /* animate */);
+                } else if (transit == TRANSIT_OLD_KEYGUARD_UNOCCLUDE) {
+                    mBinder.setOccluded(false /* isOccluded */, true /* animate */);
+                }
+                // TODO(bc-unlock): Implement occlude/unocclude animation applied on apps,
+                //  wallpapers and nonApps.
                 finishedCallback.onAnimationFinished();
             } catch (RemoteException e) {
                 Slog.e(TAG, "RemoteException");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 14a3fc0..1a17e61 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -416,7 +416,6 @@
                 }
             } else {
                 state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
-                state.label = r.getString(R.string.quick_settings_airplane_safe_label);
             }
         } else if (cb.mNoDefaultNetwork && cb.mNoNetworksAvailable) {
             state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
@@ -480,9 +479,6 @@
             state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_available);
             state.secondaryLabel = r.getString(R.string.quick_settings_networks_available);
         } else {
-            if (cb.mAirplaneModeEnabled) {
-                state.label = r.getString(R.string.quick_settings_airplane_safe_label);
-            }
             state.icon = new SignalIcon(cb.mMobileSignalIconId);
             state.secondaryLabel = appendMobileDataType(cb.mDataSubscriptionName,
                     getMobileDataContentName(cb));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index d3065aa..8c21e76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -349,7 +349,9 @@
         invalidateOutline();
         selectLayout(false /* animate */, mForceSelectNextLayout /* force */);
         mForceSelectNextLayout = false;
-        updateExpandButtons(mExpandable);
+        // TODO(b/182314698): move this to onMeasure.  This requires switching to getMeasuredHeight,
+        //  and also requires revisiting all of the logic called earlier in this method.
+        updateExpandButtonsDuringLayout(mExpandable, true /* duringLayout */);
     }
 
     @Override
@@ -1344,9 +1346,7 @@
         }
         ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
         View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
-        LinearLayout actionContainerLayout =
-                layout.findViewById(com.android.internal.R.id.actions_container_layout);
-        if (bubbleButton == null || actionContainer == null || actionContainerLayout == null) {
+        if (bubbleButton == null || actionContainer == null) {
             return;
         }
         boolean isPersonWithShortcut =
@@ -1589,6 +1589,10 @@
     }
 
     public void updateExpandButtons(boolean expandable) {
+        updateExpandButtonsDuringLayout(expandable, false /* duringLayout */);
+    }
+
+    private void updateExpandButtonsDuringLayout(boolean expandable, boolean duringLayout) {
         mExpandable = expandable;
         // if the expanded child has the same height as the collapsed one we hide it.
         if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
@@ -1602,14 +1606,15 @@
                 expandable = false;
             }
         }
+        boolean requestLayout = duringLayout && mIsContentExpandable != expandable;
         if (mExpandedChild != null) {
-            mExpandedWrapper.updateExpandability(expandable, mExpandClickListener);
+            mExpandedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
         }
         if (mContractedChild != null) {
-            mContractedWrapper.updateExpandability(expandable, mExpandClickListener);
+            mContractedWrapper.updateExpandability(expandable, mExpandClickListener, requestLayout);
         }
         if (mHeadsUpChild != null) {
-            mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener);
+            mHeadsUpWrapper.updateExpandability(expandable,  mExpandClickListener, requestLayout);
         }
         mIsContentExpandable = expandable;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index fb0fdcc..383bb7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -147,8 +147,11 @@
     override fun setRemoteInputVisible(visible: Boolean) =
             conversationLayout.showHistoricMessages(visible)
 
-    override fun updateExpandability(expandable: Boolean, onClickListener: View.OnClickListener?) =
-            conversationLayout.updateExpandability(expandable, onClickListener)
+    override fun updateExpandability(
+        expandable: Boolean,
+        onClickListener: View.OnClickListener,
+        requestLayout: Boolean
+    ) = conversationLayout.updateExpandability(expandable, onClickListener)
 
     override fun disallowSingleClick(x: Float, y: Float): Boolean {
         val isOnExpandButton = expandBtnContainer.visibility == View.VISIBLE &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index bdafd23..5a55545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -261,7 +261,8 @@
     }
 
     @Override
-    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
+    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+            boolean requestLayout) {
         mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
         mExpandButton.setOnClickListener(expandable ? onClickListener : null);
         if (mAltExpandTarget != null) {
@@ -273,6 +274,13 @@
         if (mNotificationHeader != null) {
             mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
         }
+        // Unfortunately, the NotificationContentView has to layout its children in order to
+        // determine their heights, and that affects the button visibility.  If that happens
+        // (thankfully it is rare) then we need to request layout of the expand button's parent
+        // in order to ensure it gets laid out correctly.
+        if (requestLayout) {
+            mExpandButton.getParent().requestLayout();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 9ced12d..3a7b461 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -291,8 +291,10 @@
      *
      * @param expandable should this view be expandable
      * @param onClickListener the listener to invoke when the expand affordance is clicked on
+     * @param requestLayout the expandability changed during onLayout, so a requestLayout required
      */
-    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
+    public void updateExpandability(boolean expandable, View.OnClickListener onClickListener,
+            boolean requestLayout) {}
 
     /** Set the expanded state on the view wrapper */
     public void setExpanded(boolean expanded) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index f65ae0c..1d30736 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -324,7 +324,7 @@
         StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
         final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
                 notification.getNotification());
-        RemoteViews header = builder.makeNotificationHeader();
+        RemoteViews header = builder.makeNotificationGroupHeader();
         if (mNotificationHeader == null) {
             mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
             mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 1d18750..ddfa63a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -480,7 +480,7 @@
     @WMSingleton
     @Provides
     static StartingWindowController provideStartingWindowController(Context context,
-            @ShellAnimationThread ShellExecutor executor, TransactionPool pool) {
+            @ShellSplashscreenThread ShellExecutor executor, TransactionPool pool) {
         return new StartingWindowController(context, executor, pool);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 49ba646..6272277 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -16,13 +16,19 @@
 
 package com.android.keyguard;
 
+import static android.view.WindowInsets.Type.ime;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.WindowInsetsController;
@@ -33,6 +39,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.FalsingManager;
@@ -84,18 +91,34 @@
     @Mock
     private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
     @Mock
+    private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
+    @Mock
+    private KeyguardMessageArea mKeyguardMessageArea;
+    @Mock
     private ConfigurationController mConfigurationController;
     @Mock
     private KeyguardViewController mKeyguardViewController;
     private FalsingManager mFalsingManager = new FalsingManagerFake();
 
     private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
+    private KeyguardPasswordViewController mKeyguardPasswordViewController;
+    private KeyguardPasswordView mKeyguardPasswordView;
 
     @Before
     public void setup() {
         when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
                 .thenReturn(mAdminSecondaryLockScreenController);
         when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+        mKeyguardPasswordView = spy(new KeyguardPasswordView(getContext()));
+        when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
+        when(mKeyguardPasswordView.findViewById(R.id.keyguard_message_area))
+                .thenReturn(mKeyguardMessageArea);
+        when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
+        mKeyguardPasswordViewController = new KeyguardPasswordViewController(
+                (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
+                SecurityMode.Password, mLockPatternUtils, null,
+                mKeyguardMessageAreaControllerFactory, null, null, null, mock(Resources.class),
+                null);
 
         mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory(
                 mView,  mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
@@ -125,14 +148,13 @@
     public void startDisappearAnimation_animatesKeyboard() {
         when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
                 SecurityMode.Password);
-        when(mInputViewController.getSecurityMode()).thenReturn(
-                SecurityMode.Password);
         when(mKeyguardSecurityViewFlipperController.getSecurityView(
                 eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
-                .thenReturn(mInputViewController);
-        mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */);
+                .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+        mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
 
         mKeyguardSecurityContainerController.startDisappearAnimation(null);
-        verify(mInputViewController).startDisappearAnimation(eq(null));
+        verify(mWindowInsetsController).controlWindowInsetsAnimation(
+                eq(ime()), anyLong(), any(), any(), any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 1783fa4..104318e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -19,9 +19,6 @@
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.systemBars;
 
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -90,13 +87,6 @@
     }
 
     @Test
-    public void startDisappearAnimation_animatesKeyboard() {
-        mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password);
-        verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
-                any(), any());
-    }
-
-    @Test
     public void onMeasure_usesFullWidthWithoutOneHandedMode() {
         setUpKeyguard(
                 /* deviceConfigCanUseOneHandedKeyguard= */false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index d015f51..b232850 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -35,7 +35,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
 import com.android.systemui.dock.DockManagerFake;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -72,10 +71,7 @@
     private FalsingClassifier mClassifierB;
     private final List<MotionEvent> mMotionEventList = new ArrayList<>();
     @Mock
-    private HistoryTracker mHistoryTracker;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-
+    private HistoryTracker mHistoryTracker;;
     private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
 
     private final FalsingClassifier.Result mFalsedResult = FalsingClassifier.Result.falsed(1, "");
@@ -92,10 +88,9 @@
         mClassifiers.add(mClassifierA);
         mClassifiers.add(mClassifierB);
         when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
         mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mDockManager,
                 mMetricsLogger, mClassifiers, mSingleTapClassfier, mDoubleTapClassifier,
-                mHistoryTracker, mKeyguardStateController, false);
+                mHistoryTracker, false);
 
 
         ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
@@ -125,7 +120,7 @@
     }
 
     @Test
-    public void testIsFalseTouch_ClassifiersPass() {
+    public void testIsFalseTouch_ClassffiersPass() {
         assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
     }
 
@@ -238,18 +233,4 @@
 
         assertThat(mFakeExecutor.numPending()).isEqualTo(0);
     }
-
-    @Test
-    public void testNoFalsingUnlocked() {
-        when(mKeyguardStateController.isShowing()).thenReturn(false);
-
-        when(mClassifierA.classifyGesture(anyDouble(), anyDouble())).thenReturn(mFalsedResult);
-        assertThat(mBrightLineFalsingManager.isFalseTouch(0)).isFalse();
-
-        when(mSingleTapClassfier.isTap(mMotionEventList)).thenReturn(mFalsedResult);
-        assertThat(mBrightLineFalsingManager.isFalseTap(false, 0)).isFalse();
-
-        when(mDoubleTapClassifier.classifyGesture()).thenReturn(mFalsedResult);
-        assertThat(mBrightLineFalsingManager.isFalseDoubleTap()).isFalse();
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index e6aeee7..dc79b88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -34,7 +34,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.sensors.ProximitySensor;
 import com.android.systemui.util.sensors.ThresholdSensor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -63,19 +62,16 @@
     private ProximitySensor mProximitySensor;
     @Mock
     private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
-        when(mKeyguardStateController.isShowing()).thenReturn(true);
 
         mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager,
                 mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor,
-                mStatusBarStateController, mKeyguardStateController, new FakeSystemClock());
+                mStatusBarStateController, new FakeSystemClock());
     }
 
     @Test
@@ -163,20 +159,4 @@
         mFalsingCollector.onTouchEvent(up);
         verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
     }
-
-    @Test
-    public void testAvoidUnlocked() {
-        MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
-        MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0);
-
-        when(mKeyguardStateController.isShowing()).thenReturn(false);
-
-        // Nothing passed initially
-        mFalsingCollector.onTouchEvent(down);
-        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
-
-        // Up event would normally flush the up event.
-        mFalsingCollector.onTouchEvent(up);
-        verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 9278570..17eb15b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -63,6 +64,8 @@
     private lateinit var taskViewFactory: Optional<TaskViewFactory>
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var cvh: ControlViewHolder
+    @Mock
+    private lateinit var metricsLogger: ControlsMetricsLogger
 
     companion object {
         fun <T> any(): T = Mockito.any<T>()
@@ -86,7 +89,8 @@
             globalActionsComponent,
             taskViewFactory,
             getFakeBroadcastDispatcher(),
-            lazyUiController
+            lazyUiController,
+            metricsLogger
         ))
 
         `when`(cvh.cws.ci.controlId).thenReturn(ID)
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
index f8b9309..f0de811 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java
@@ -360,7 +360,7 @@
             try {
                 mCallback.setProxyPort(port);
             } catch (RemoteException e) {
-                Log.w(TAG, "Proxy failed to report port to PacProxyInstaller", e);
+                Log.w(TAG, "Proxy failed to report port to PacProxyService", e);
             }
         }
         mPort = port;
@@ -371,7 +371,7 @@
             try {
                 callback.setProxyPort(mPort);
             } catch (RemoteException e) {
-                Log.w(TAG, "Proxy failed to report port to PacProxyInstaller", e);
+                Log.w(TAG, "Proxy failed to report port to PacProxyService", e);
             }
         }
         mCallback = callback;
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
index bdf478d..a8e2622 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
@@ -30,7 +30,7 @@
 
     private static ProxyServer server = null;
 
-    /** Keep these values up-to-date with PacProxyInstaller.java */
+    /** Keep these values up-to-date with PacProxyService.java */
     public static final String KEY_PROXY = "keyProxy";
     public static final String HOST = "localhost";
     public static final String EXCL_LIST = "";
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
index c7f0efa..3d07da5 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java
@@ -118,6 +118,7 @@
 
         private static final int INVALID_ID = -1;
         private int mIdOfLastServiceToMagnify = INVALID_ID;
+        private boolean mMagnificationActivated = false;
 
         DisplayMagnification(int displayId) {
             mDisplayId = displayId;
@@ -322,6 +323,13 @@
                         mSpecAnimationBridge, spec, animationCallback);
                 mControllerCtx.getHandler().sendMessage(m);
             }
+
+            final boolean lastMagnificationActivated = mMagnificationActivated;
+            mMagnificationActivated = spec.scale > 1.0f;
+            if (mMagnificationActivated != lastMagnificationActivated) {
+                mMagnificationRequestObserver.onFullScreenMagnificationActivationState(
+                        mMagnificationActivated);
+            }
         }
 
         /**
@@ -1506,5 +1514,14 @@
          * @param serviceId the ID of the service requesting the change
          */
         void onRequestMagnificationSpec(int displayId, int serviceId);
+
+        /**
+         * Called when the state of the magnification activation is changed.
+         * It is for the logging data of the magnification activation state.
+         *
+         * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
+         */
+        @GuardedBy("mLock")
+        void onFullScreenMagnificationActivationState(boolean activated);
     }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 2a65b64..17a7d39 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 
@@ -25,11 +26,13 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.accessibility.MagnificationAnimationCallback;
 
+import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.accessibility.AccessibilityManagerService;
 
@@ -51,7 +54,8 @@
  * </ol>
  */
 public class MagnificationController implements WindowMagnificationManager.Callback,
-        MagnificationGestureHandler.Callback {
+        MagnificationGestureHandler.Callback,
+        FullScreenMagnificationController.MagnificationRequestObserver {
 
     private static final boolean DEBUG = false;
     private static final String TAG = "MagnificationController";
@@ -66,6 +70,9 @@
     private WindowMagnificationManager mWindowMagnificationMgr;
     private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 
+    private long mWindowModeEnabledTime = 0;
+    private long mFullScreenModeEnabledTime = 0;
+
     /**
      * A callback to inform the magnification transition result.
      */
@@ -187,7 +194,8 @@
         setDisableMagnificationCallbackLocked(displayId, animationEndCallback);
     }
 
-    void onRequestMagnificationSpec(int displayId, int serviceId) {
+    @Override
+    public void onRequestMagnificationSpec(int displayId, int serviceId) {
         synchronized (mLock) {
             if (serviceId == AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID) {
                 return;
@@ -200,6 +208,39 @@
         }
     }
 
+    // TODO : supporting multi-display (b/182227245).
+    @Override
+    public void onWindowMagnificationActivationState(boolean activated) {
+        if (activated) {
+            mWindowModeEnabledTime = SystemClock.uptimeMillis();
+        } else {
+            logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
+                    SystemClock.uptimeMillis() - mWindowModeEnabledTime);
+        }
+    }
+
+    @Override
+    public void onFullScreenMagnificationActivationState(boolean activated) {
+        if (activated) {
+            mFullScreenModeEnabledTime = SystemClock.uptimeMillis();
+        } else {
+            logMagnificationUsageState(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN,
+                    SystemClock.uptimeMillis() - mFullScreenModeEnabledTime);
+        }
+    }
+
+    /**
+     * Wrapper method of logging the magnification activated mode and its duration of the usage
+     * when the magnification is disabled.
+     *
+     * @param mode The activated magnification mode.
+     * @param duration The duration in milliseconds during the magnification is activated.
+     */
+    @VisibleForTesting
+    public void logMagnificationUsageState(int mode, long duration) {
+        AccessibilityStatsLogUtils.logMagnificationUsageState(mode, duration);
+    }
+
     /**
      * Updates the active user ID of {@link FullScreenMagnificationController} and {@link
      * WindowMagnificationManager}.
@@ -260,7 +301,7 @@
         synchronized (mLock) {
             if (mFullScreenMagnificationController == null) {
                 mFullScreenMagnificationController = new FullScreenMagnificationController(mContext,
-                        mAms, mLock, this::onRequestMagnificationSpec);
+                        mAms, mLock, this);
                 mFullScreenMagnificationController.setUserId(mAms.getCurrentUserIdLocked());
             }
         }
@@ -340,7 +381,7 @@
             mTransitionCallBack = transitionCallBack;
             mDisplayId = displayId;
             mTargetMode = targetMode;
-            mCurrentMode = mTargetMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
+            mCurrentMode = mTargetMode ^ ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
             mCurrentScale = scale;
             mCurrentCenter.set(currentCenter);
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
index 40668d8..ded601e 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationManager.java
@@ -93,6 +93,13 @@
          * @param scale the target scale, or {@link Float#NaN} to leave unchanged
          */
         void onPerformScaleAction(int displayId, float scale);
+
+        /**
+         * Called when the state of the magnification activation is changed.
+         *
+         * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
+         */
+        void onWindowMagnificationActivationState(boolean activated);
     }
 
     private final Callback mCallback;
@@ -264,6 +271,7 @@
      */
     void enableWindowMagnification(int displayId, float scale, float centerX, float centerY,
             @Nullable MagnificationAnimationCallback animationCallback) {
+        final boolean enabled;
         synchronized (mLock) {
             if (mConnectionWrapper == null) {
                 return;
@@ -272,9 +280,13 @@
             if (magnifier == null) {
                 magnifier = createWindowMagnifier(displayId);
             }
-            magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
+            enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
                     animationCallback);
         }
+
+        if (enabled) {
+            mCallback.onWindowMagnificationActivationState(true);
+        }
     }
 
     /**
@@ -296,16 +308,21 @@
      */
     void disableWindowMagnification(int displayId, boolean clear,
             MagnificationAnimationCallback animationCallback) {
+        final boolean disabled;
         synchronized (mLock) {
             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
             if (magnifier == null || mConnectionWrapper == null) {
                 return;
             }
-            magnifier.disableWindowMagnificationInternal(animationCallback);
+            disabled = magnifier.disableWindowMagnificationInternal(animationCallback);
             if (clear) {
                 mWindowMagnifiers.delete(displayId);
             }
         }
+
+        if (disabled) {
+            mCallback.onWindowMagnificationActivationState(false);
+        }
     }
 
     /**
@@ -560,26 +577,35 @@
         }
 
         @GuardedBy("mLock")
-        void enableWindowMagnificationInternal(float scale, float centerX, float centerY,
+        boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
                 @Nullable MagnificationAnimationCallback animationCallback) {
             if (mEnabled) {
-                return;
+                return false;
             }
             final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
             if (mWindowMagnificationManager.enableWindowMagnificationInternal(mDisplayId, normScale,
                     centerX, centerY, animationCallback)) {
                 mScale = normScale;
                 mEnabled = true;
+
+                return true;
             }
+            return false;
         }
 
         @GuardedBy("mLock")
-        void disableWindowMagnificationInternal(
+        boolean disableWindowMagnificationInternal(
                 @Nullable MagnificationAnimationCallback animationResultCallback) {
-            if (mEnabled && mWindowMagnificationManager.disableWindowMagnificationInternal(
+            if (!mEnabled) {
+                return false;
+            }
+            if (mWindowMagnificationManager.disableWindowMagnificationInternal(
                     mDisplayId, animationResultCallback)) {
                 mEnabled = false;
+
+                return true;
             }
+            return false;
         }
 
         @GuardedBy("mLock")
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index f07bd2d..148df09 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -1001,7 +1001,8 @@
     private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) {
         return CollectionUtils.filter(
                 getAllAssociations(userId),
-                a -> Objects.equals(packageFilter, a.getPackageName()));
+                // Null filter == get all associations
+                a -> packageFilter == null || Objects.equals(packageFilter, a.getPackageName()));
     }
 
     private Set<Association> getAllAssociations() {
@@ -1022,8 +1023,10 @@
             int userId, @Nullable String packageFilter, @Nullable String addressFilter) {
         return CollectionUtils.filter(
                 getAllAssociations(userId),
-                a -> Objects.equals(packageFilter, a.getPackageName())
-                        && Objects.equals(addressFilter, a.getDeviceMacAddress()));
+                // Null filter == get all associations
+                a -> (packageFilter == null || Objects.equals(packageFilter, a.getPackageName()))
+                        && (addressFilter == null
+                                || Objects.equals(addressFilter, a.getDeviceMacAddress())));
     }
 
     private Set<Association> readAllAssociations(int userId) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b4fcaee..688b33e 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4425,7 +4425,8 @@
                     break;
                 }
                 case EVENT_PROXY_HAS_CHANGED: {
-                    handleApplyDefaultProxy((ProxyInfo)msg.obj);
+                    final Pair<Network, ProxyInfo> arg = (Pair<Network, ProxyInfo>) msg.obj;
+                    handleApplyDefaultProxy(arg.second);
                     break;
                 }
                 case EVENT_REGISTER_NETWORK_PROVIDER: {
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index a353519..96ff900 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -16,6 +16,8 @@
 
 package com.android.server;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.util.Log;
 
@@ -51,4 +53,9 @@
             publishBinderService(Context.CONTEXTHUB_SERVICE, mContextHubService);
         }
     }
+
+    @Override
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        mContextHubService.onUserChanged();
+    }
 }
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 35b730a..ebe5dd2 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -962,7 +962,7 @@
             }
 
             int delay = DeviceConfig.getInt(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
-                    ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 0);
+                    ANR_DELAY_MILLIS_DEVICE_CONFIG_KEY, 5000);
             Slog.v(TAG, "getAnrDelayMillis for " + packageName + ". " + delay + "ms");
             return delay;
         }
@@ -1791,7 +1791,7 @@
     public StorageManagerService(Context context) {
         sSelf = this;
         mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
-                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mContext = context;
         mResolver = mContext.getContentResolver();
         mCallbacks = new Callbacks(FgThread.get().getLooper());
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index ee61067..f566277 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -90,7 +90,12 @@
         mCm = mContext.getSystemService(ConnectivityManager.class);
         mNetworkProvider = new NetworkProvider(mContext, mHandler.getLooper(),
                 TEST_NETWORK_PROVIDER_NAME);
-        mCm.registerNetworkProvider(mNetworkProvider);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mCm.registerNetworkProvider(mNetworkProvider);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ed8d696..4972aa7 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -786,7 +786,7 @@
         mAppDataIsolationEnabled =
                 SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
-                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
         mAppDataIsolationAllowlistedApps = new ArrayList<>(
                 SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
 
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 6712c54..5bf15dc 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -43,6 +44,8 @@
 import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.IUserManager;
 import android.os.Parcel;
@@ -55,10 +58,15 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.autofill.AutofillManagerInternal;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
 import android.widget.Toast;
 
 import com.android.internal.R;
@@ -170,6 +178,8 @@
     // DeviceConfig properties
     private static final String PROPERTY_SHOW_ACCESS_NOTIFICATIONS = "show_access_notifications";
     private static final boolean DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true;
+    private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
+    private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;
 
     private final ActivityManagerInternal mAmInternal;
     private final IUriGrantsManager mUgm;
@@ -180,8 +190,10 @@
     private final AppOpsManager mAppOps;
     private final ContentCaptureManagerInternal mContentCaptureInternal;
     private final AutofillManagerInternal mAutofillInternal;
+    private final TextClassificationManager mTextClassificationManager;
     private final IBinder mPermissionOwner;
     private final HostClipboardMonitor mHostClipboardMonitor;
+    private final Handler mWorkerHandler;
 
     @GuardedBy("mLock")
     private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
@@ -189,6 +201,9 @@
     @GuardedBy("mLock")
     private boolean mShowAccessNotifications = DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
 
+    @GuardedBy("mLock")
+    private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;
+
     private final Object mLock = new Object();
 
     /**
@@ -206,6 +221,8 @@
         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
         mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class);
         mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class);
+        mTextClassificationManager = (TextClassificationManager)
+                getContext().getSystemService(Context.TEXT_CLASSIFICATION_SERVICE);
         final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
         mPermissionOwner = permOwner;
         if (IS_EMULATOR) {
@@ -232,6 +249,10 @@
         updateConfig();
         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
                 getContext().getMainExecutor(), properties -> updateConfig());
+
+        HandlerThread workerThread = new HandlerThread(TAG);
+        workerThread.start();
+        mWorkerHandler = workerThread.getThreadHandler();
     }
 
     @Override
@@ -250,6 +271,8 @@
         synchronized (mLock) {
             mShowAccessNotifications = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
                     PROPERTY_SHOW_ACCESS_NOTIFICATIONS, DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
+            mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
+                    PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
         }
     }
 
@@ -592,6 +615,10 @@
             }
         }
 
+        if (clip != null) {
+            startClassificationLocked(clip);
+        }
+
         // Update this user
         final int userId = UserHandle.getUserId(uid);
         setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);
@@ -691,6 +718,68 @@
         }
     }
 
+    @GuardedBy("mLock")
+    private void startClassificationLocked(@NonNull ClipData clip) {
+        TextClassifier classifier;
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            classifier = mTextClassificationManager.createTextClassificationSession(
+                    new TextClassificationContext.Builder(
+                            getContext().getPackageName(),
+                            TextClassifier.WIDGET_TYPE_CLIPBOARD
+                    ).build()
+            );
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        if (clip.getItemCount() == 0) {
+            clip.getDescription().setClassificationStatus(
+                    ClipDescription.CLASSIFICATION_NOT_PERFORMED);
+            return;
+        }
+        CharSequence text = clip.getItemAt(0).getText();
+        if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength
+                || text.length() > classifier.getMaxGenerateLinksTextLength()) {
+            clip.getDescription().setClassificationStatus(
+                    ClipDescription.CLASSIFICATION_NOT_PERFORMED);
+            return;
+        }
+
+        mWorkerHandler.post(() -> doClassification(text, clip, classifier));
+    }
+
+    @WorkerThread
+    private void doClassification(
+            CharSequence text, ClipData clip, TextClassifier classifier) {
+        TextLinks.Request request = new TextLinks.Request.Builder(text).build();
+        TextLinks links;
+        try {
+            links = classifier.generateLinks(request);
+        } finally {
+            classifier.destroy();
+        }
+
+        // Find the highest confidence for each entity in the text.
+        ArrayMap<String, Float> confidences = new ArrayMap<>();
+        for (TextLinks.TextLink link : links.getLinks()) {
+            for (int i = 0; i < link.getEntityCount(); i++) {
+                String entity = link.getEntity(i);
+                float conf = link.getConfidenceScore(entity);
+                if (conf > confidences.getOrDefault(entity, 0f)) {
+                    confidences.put(entity, conf);
+                }
+            }
+        }
+
+        synchronized (mLock) {
+            clip.getDescription().setConfidenceScores(confidences);
+            if (!links.getLinks().isEmpty()) {
+                clip.getItemAt(0).setTextLinks(links);
+            }
+        }
+    }
+
     private boolean isDeviceLocked(@UserIdInt int userId) {
         final long token = Binder.clearCallingIdentity();
         try {
@@ -922,6 +1011,10 @@
         if (!mShowAccessNotifications) {
             return;
         }
+        if (Settings.Secure.getInt(getContext().getContentResolver(),
+                Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, 1) == 0) {
+            return;
+        }
         // Don't notify if the app accessing the clipboard is the same as the current owner.
         if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
             return;
diff --git a/services/core/java/com/android/server/connectivity/DnsManager.java b/services/core/java/com/android/server/connectivity/DnsManager.java
index 4f6b530..7a5abf8 100644
--- a/services/core/java/com/android/server/connectivity/DnsManager.java
+++ b/services/core/java/com/android/server/connectivity/DnsManager.java
@@ -16,7 +16,6 @@
 
 package com.android.server.connectivity;
 
-import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
 import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
 import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
@@ -33,6 +32,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.net.ConnectivityManager;
 import android.net.IDnsResolver;
 import android.net.InetAddresses;
 import android.net.LinkProperties;
@@ -128,7 +128,7 @@
     private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
 
     public static PrivateDnsConfig getPrivateDnsConfig(ContentResolver cr) {
-        final String mode = getPrivateDnsMode(cr);
+        final String mode = ConnectivityManager.getPrivateDnsMode(cr);
 
         final boolean useTls = !TextUtils.isEmpty(mode) && !PRIVATE_DNS_MODE_OFF.equals(mode);
 
@@ -479,13 +479,6 @@
         return result;
     }
 
-    private static String getPrivateDnsMode(ContentResolver cr) {
-        String mode = getStringSetting(cr, PRIVATE_DNS_MODE);
-        if (TextUtils.isEmpty(mode)) mode = getStringSetting(cr, PRIVATE_DNS_DEFAULT_MODE);
-        if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
-        return mode;
-    }
-
     private static String getStringSetting(ContentResolver cr, String which) {
         return Settings.Global.getString(cr, which);
     }
diff --git a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java b/services/core/java/com/android/server/connectivity/PacProxyService.java
similarity index 75%
rename from services/core/java/com/android/server/connectivity/PacProxyInstaller.java
rename to services/core/java/com/android/server/connectivity/PacProxyService.java
index aadaf4d..d23b488 100644
--- a/services/core/java/com/android/server/connectivity/PacProxyInstaller.java
+++ b/services/core/java/com/android/server/connectivity/PacProxyService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.WorkerThread;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
@@ -26,12 +28,16 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.net.IPacProxyInstalledListener;
+import android.net.IPacProxyManager;
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -44,6 +50,7 @@
 import com.android.net.IProxyCallback;
 import com.android.net.IProxyPortListener;
 import com.android.net.IProxyService;
+import com.android.net.module.util.PermissionUtils;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -53,7 +60,7 @@
 /**
  * @hide
  */
-public class PacProxyInstaller {
+public class PacProxyService extends IPacProxyManager.Stub {
     private static final String PAC_PACKAGE = "com.android.pacprocessor";
     private static final String PAC_SERVICE = "com.android.pacprocessor.PacService";
     private static final String PAC_SERVICE_NAME = "com.android.net.IProxyService";
@@ -61,7 +68,7 @@
     private static final String PROXY_PACKAGE = "com.android.proxyhandler";
     private static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService";
 
-    private static final String TAG = "PacProxyInstaller";
+    private static final String TAG = "PacProxyService";
 
     private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH";
 
@@ -71,10 +78,6 @@
     private static final int DELAY_LONG = 4;
     private static final long MAX_PAC_SIZE = 20 * 1000 * 1000;
 
-    // Return values for #setCurrentProxyScriptUrl
-    public static final boolean DONT_SEND_BROADCAST = false;
-    public static final boolean DO_SEND_BROADCAST = true;
-
     private String mCurrentPac;
     @GuardedBy("mProxyLock")
     private volatile Uri mPacUrl = Uri.EMPTY;
@@ -93,8 +96,8 @@
     private volatile boolean mHasSentBroadcast;
     private volatile boolean mHasDownloaded;
 
-    private Handler mConnectivityHandler;
-    private final int mProxyMessage;
+    private final RemoteCallbackList<IPacProxyInstalledListener>
+            mCallbacks = new RemoteCallbackList<>();
 
     /**
      * Used for locking when setting mProxyService and all references to mCurrentPac.
@@ -102,6 +105,13 @@
     private final Object mProxyLock = new Object();
 
     /**
+     * Lock ensuring consistency between the values of mHasSentBroadcast, mHasDownloaded, the
+     * last URL and port, and the broadcast message being sent with the correct arguments.
+     * TODO : this should probably protect all instances of these variables
+     */
+    private final Object mBroadcastStateLock = new Object();
+
+    /**
      * Runnable to download PAC script.
      * The behavior relies on the assumption it always runs on mNetThread to guarantee that the
      * latest data fetched from mPacUrl is stored in mProxyService.
@@ -146,10 +156,10 @@
         }
     }
 
-    public PacProxyInstaller(Context context, Handler handler, int proxyMessage) {
+    public PacProxyService(@NonNull Context context) {
         mContext = context;
         mLastPort = -1;
-        final HandlerThread netThread = new HandlerThread("android.pacproxyinstaller",
+        final HandlerThread netThread = new HandlerThread("android.pacproxyservice",
                 android.os.Process.THREAD_PRIORITY_DEFAULT);
         netThread.start();
         mNetThreadHandler = new Handler(netThread.getLooper());
@@ -158,8 +168,6 @@
                 context, 0, new Intent(ACTION_PAC_REFRESH), PendingIntent.FLAG_IMMUTABLE);
         context.registerReceiver(new PacRefreshIntentReceiver(),
                 new IntentFilter(ACTION_PAC_REFRESH));
-        mConnectivityHandler = handler;
-        mProxyMessage = proxyMessage;
     }
 
     private AlarmManager getAlarmManager() {
@@ -169,38 +177,52 @@
         return mAlarmManager;
     }
 
+    @Override
+    public void addListener(IPacProxyInstalledListener listener) {
+        PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+                android.Manifest.permission.NETWORK_SETTINGS);
+        mCallbacks.register(listener);
+    }
+
+    @Override
+    public void removeListener(IPacProxyInstalledListener listener) {
+        PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+                android.Manifest.permission.NETWORK_SETTINGS);
+        mCallbacks.unregister(listener);
+    }
+
     /**
      * Updates the PAC Proxy Installer with current Proxy information. This is called by
-     * the ProxyTracker directly before a broadcast takes place to allow
-     * the PacProxyInstaller to indicate that the broadcast should not be sent and the
-     * PacProxyInstaller will trigger a new broadcast when it is ready.
+     * the ProxyTracker through PacProxyManager before a broadcast takes place to allow
+     * the PacProxyService to indicate that the broadcast should not be sent and the
+     * PacProxyService will trigger a new broadcast when it is ready.
      *
      * @param proxy Proxy information that is about to be broadcast.
-     * @return Returns whether the broadcast should be sent : either DO_ or DONT_SEND_BROADCAST
      */
-    public synchronized boolean setCurrentProxyScriptUrl(ProxyInfo proxy) {
-        if (!Uri.EMPTY.equals(proxy.getPacFileUrl())) {
-            if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
-                // Allow to send broadcast, nothing to do.
-                return DO_SEND_BROADCAST;
-            }
-            mPacUrl = proxy.getPacFileUrl();
-            mCurrentDelay = DELAY_1;
-            mHasSentBroadcast = false;
-            mHasDownloaded = false;
-            getAlarmManager().cancel(mPacRefreshIntent);
-            bind();
-            return DONT_SEND_BROADCAST;
-        } else {
-            getAlarmManager().cancel(mPacRefreshIntent);
-            synchronized (mProxyLock) {
-                mPacUrl = Uri.EMPTY;
-                mCurrentPac = null;
-                if (mProxyService != null) {
-                    unbind();
+    @Override
+    public void setCurrentProxyScriptUrl(@Nullable ProxyInfo proxy) {
+        PermissionUtils.enforceNetworkStackPermissionOr(mContext,
+                android.Manifest.permission.NETWORK_SETTINGS);
+
+        synchronized (mBroadcastStateLock) {
+            if (proxy != null && !Uri.EMPTY.equals(proxy.getPacFileUrl())) {
+                if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) return;
+                mPacUrl = proxy.getPacFileUrl();
+                mCurrentDelay = DELAY_1;
+                mHasSentBroadcast = false;
+                mHasDownloaded = false;
+                getAlarmManager().cancel(mPacRefreshIntent);
+                bind();
+            } else {
+                getAlarmManager().cancel(mPacRefreshIntent);
+                synchronized (mProxyLock) {
+                    mPacUrl = Uri.EMPTY;
+                    mCurrentPac = null;
+                    if (mProxyService != null) {
+                        unbind();
+                    }
                 }
             }
-            return DO_SEND_BROADCAST;
         }
     }
 
@@ -275,6 +297,7 @@
         getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent);
     }
 
+    @GuardedBy("mProxyLock")
     private void setCurrentProxyScript(String script) {
         if (mProxyService == null) {
             Log.e(TAG, "setCurrentProxyScript: no proxy service");
@@ -347,6 +370,9 @@
                             public void setProxyPort(int port) {
                                 if (mLastPort != -1) {
                                     // Always need to send if port changed
+                                    // TODO: Here lacks synchronization because this write cannot
+                                    // guarantee that it's visible from sendProxyIfNeeded() when
+                                    // it's called by a Runnable which is post by mNetThread.
                                     mHasSentBroadcast = false;
                                 }
                                 mLastPort = port;
@@ -365,8 +391,9 @@
                 }
             }
         };
-        mContext.bindService(intent, mProxyConnection,
-                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
+        mContext.bindService(intent,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
+                new HandlerExecutor(mNetThreadHandler), mProxyConnection);
     }
 
     private void unbind() {
@@ -383,16 +410,28 @@
     }
 
     private void sendPacBroadcast(ProxyInfo proxy) {
-        mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
+        final int length = mCallbacks.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            final IPacProxyInstalledListener listener = mCallbacks.getBroadcastItem(i);
+            if (listener != null) {
+                try {
+                    listener.onPacProxyInstalled(null /* network */, proxy);
+                } catch (RemoteException ignored) { }
+            }
+        }
+        mCallbacks.finishBroadcast();
     }
 
-    private synchronized void sendProxyIfNeeded() {
-        if (!mHasDownloaded || (mLastPort == -1)) {
-            return;
-        }
-        if (!mHasSentBroadcast) {
-            sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
-            mHasSentBroadcast = true;
+    // This method must be called on mNetThreadHandler.
+    private void sendProxyIfNeeded() {
+        synchronized (mBroadcastStateLock) {
+            if (!mHasDownloaded || (mLastPort == -1)) {
+                return;
+            }
+            if (!mHasSentBroadcast) {
+                sendPacBroadcast(ProxyInfo.buildPacProxy(mPacUrl, mLastPort));
+                mHasSentBroadcast = true;
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/ProxyTracker.java b/services/core/java/com/android/server/connectivity/ProxyTracker.java
index d83ff83..8b9c836 100644
--- a/services/core/java/com/android/server/connectivity/ProxyTracker.java
+++ b/services/core/java/com/android/server/connectivity/ProxyTracker.java
@@ -27,15 +27,19 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Network;
+import android.net.PacProxyManager;
 import android.net.Proxy;
 import android.net.ProxyInfo;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.net.module.util.ProxyUtils;
@@ -67,7 +71,7 @@
     // is not set. Individual networks have their own settings that override this. This member
     // is set through setDefaultProxy, which is called when the default network changes proxies
     // in its LinkProperties, or when ConnectivityService switches to a new default network, or
-    // when PacProxyInstaller resolves the proxy.
+    // when PacProxyService resolves the proxy.
     @Nullable
     @GuardedBy("mProxyLock")
     private volatile ProxyInfo mDefaultProxy = null;
@@ -77,16 +81,31 @@
 
     private final Handler mConnectivityServiceHandler;
 
-    // The object responsible for Proxy Auto Configuration (PAC).
-    @NonNull
-    private final PacProxyInstaller mPacProxyInstaller;
+    private final PacProxyManager mPacProxyManager;
+
+    private class PacProxyInstalledListener implements PacProxyManager.PacProxyInstalledListener {
+        private final int mEvent;
+
+        PacProxyInstalledListener(int event) {
+            mEvent = event;
+        }
+
+        public void onPacProxyInstalled(@Nullable Network network, @NonNull ProxyInfo proxy) {
+            mConnectivityServiceHandler
+                    .sendMessage(mConnectivityServiceHandler
+                    .obtainMessage(mEvent, new Pair<>(network, proxy)));
+        }
+    }
 
     public ProxyTracker(@NonNull final Context context,
             @NonNull final Handler connectivityServiceInternalHandler, final int pacChangedEvent) {
         mContext = context;
         mConnectivityServiceHandler = connectivityServiceInternalHandler;
-        mPacProxyInstaller = new PacProxyInstaller(
-                context, connectivityServiceInternalHandler, pacChangedEvent);
+        mPacProxyManager = context.getSystemService(PacProxyManager.class);
+
+        PacProxyInstalledListener listener = new PacProxyInstalledListener(pacChangedEvent);
+        mPacProxyManager.addPacProxyInstalledListener(
+                new HandlerExecutor(mConnectivityServiceHandler), listener);
     }
 
     // Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
@@ -182,7 +201,7 @@
 
             if (!TextUtils.isEmpty(pacFileUrl)) {
                 mConnectivityServiceHandler.post(
-                        () -> mPacProxyInstaller.setCurrentProxyScriptUrl(proxyProperties));
+                        () -> mPacProxyManager.setCurrentProxyScriptUrl(proxyProperties));
             }
         }
     }
@@ -226,9 +245,9 @@
         final ProxyInfo defaultProxy = getDefaultProxy();
         final ProxyInfo proxyInfo = null != defaultProxy ?
                 defaultProxy : ProxyInfo.buildDirectProxy("", 0, Collections.emptyList());
+        mPacProxyManager.setCurrentProxyScriptUrl(proxyInfo);
 
-        if (mPacProxyInstaller.setCurrentProxyScriptUrl(proxyInfo)
-                == PacProxyInstaller.DONT_SEND_BROADCAST) {
+        if (!shouldSendBroadcast(proxyInfo)) {
             return;
         }
         if (DBG) Log.d(TAG, "sending Proxy Broadcast for " + proxyInfo);
@@ -244,6 +263,10 @@
         }
     }
 
+    private boolean shouldSendBroadcast(ProxyInfo proxy) {
+        return Uri.EMPTY.equals(proxy.getPacFileUrl()) || proxy.getPort() > 0;
+    }
+
     /**
      * Sets the global proxy in memory. Also writes the values to the global settings of the device.
      *
@@ -308,10 +331,10 @@
                 return;
             }
 
-            // This call could be coming from the PacProxyInstaller, containing the port of the
+            // This call could be coming from the PacProxyService, containing the port of the
             // local proxy. If this new proxy matches the global proxy then copy this proxy to the
             // global (to get the correct local port), and send a broadcast.
-            // TODO: Switch PacProxyInstaller to have its own message to send back rather than
+            // TODO: Switch PacProxyService to have its own message to send back rather than
             // reusing EVENT_HAS_CHANGED_PROXY and this call to handleApplyDefaultProxy.
             if ((mGlobalProxy != null) && (proxyInfo != null)
                     && (!Uri.EMPTY.equals(proxyInfo.getPacFileUrl()))
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a2b9b96..91b96dc 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -46,7 +46,6 @@
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.EventLogTags;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
 
 import java.io.PrintWriter;
 
@@ -220,12 +219,14 @@
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
-            HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) {
+            HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context,
+            HighBrightnessModeController hbmController) {
         this(new Injector(), callbacks, looper, sensorManager, lightSensor, mapper,
                 lightSensorWarmUpTime, brightnessMin, brightnessMax, dozeScaleFactor,
                 lightSensorRate, initialLightSensorRate, brighteningLightDebounceConfig,
                 darkeningLightDebounceConfig, resetAmbientLuxAfterWarmUpConfig,
-                ambientBrightnessThresholds, screenBrightnessThresholds, display, context
+                ambientBrightnessThresholds, screenBrightnessThresholds, display, context,
+                hbmController
         );
     }
 
@@ -236,7 +237,8 @@
             float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate,
             long brighteningLightDebounceConfig, long darkeningLightDebounceConfig,
             boolean resetAmbientLuxAfterWarmUpConfig, HysteresisLevels ambientBrightnessThresholds,
-            HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context) {
+            HysteresisLevels screenBrightnessThresholds, LogicalDisplay display, Context context,
+            HighBrightnessModeController hbmController) {
         mInjector = injector;
         mContext = context;
         mCallbacks = callbacks;
@@ -273,20 +275,7 @@
         mPendingForegroundAppPackageName = null;
         mForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
         mPendingForegroundAppCategory = ApplicationInfo.CATEGORY_UNDEFINED;
-
-        final DisplayDeviceConfig ddConfig =
-                display.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
-        HighBrightnessModeData hbmData =
-                ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
-
-        final Runnable hbmChangeCallback = () -> {
-            updateAutoBrightness(true /*sendUpdate*/, false /*userInitiatedChange*/);
-            // TODO: b/175937645 - Callback to DisplayManagerService to indicate a change to the HBM
-            // allowance has been made so that the brightness limits can be calculated
-            // appropriately.
-        };
-        mHbmController = new HighBrightnessModeController(mHandler, brightnessMin, brightnessMax,
-                hbmData, hbmChangeCallback);
+        mHbmController = hbmController;
     }
 
     /**
@@ -327,6 +316,7 @@
     public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
             float brightness, boolean userChangedBrightness, float adjustment,
             boolean userChangedAutoBrightnessAdjustment, int displayPolicy) {
+        mHbmController.setAutoBrightnessEnabled(enable);
         // While dozing, the application processor may be suspended which will prevent us from
         // receiving new information from the light sensor. On some devices, we may be able to
         // switch to a wake-up light sensor instead but for now we will simply disable the sensor
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 7b107b85..e44ecac 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -350,12 +350,15 @@
     private final ColorDisplayServiceInternal mCdsi;
     private final float[] mNitsRange;
 
+    private final HighBrightnessModeController mHbmController;
+
     // A record of state for skipping brightness ramps.
     private int mSkipRampState = RAMP_STATE_SKIP_NONE;
 
     // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL.
     private float mInitialAutoBrightness;
 
+
     // The controller for the automatic brightness level.
     private AutomaticBrightnessController mAutomaticBrightnessController;
 
@@ -433,6 +436,7 @@
         mBlanker = blanker;
         mContext = context;
 
+
         PowerManager pm = context.getSystemService(PowerManager.class);
 
         final Resources resources = context.getResources();
@@ -476,6 +480,8 @@
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
 
+        mHbmController = createHbmController();
+
         if (mUseSoftwareAutoBrightnessConfig) {
             final float dozeScaleFactor = resources.getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
@@ -535,7 +541,7 @@
                         PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
                         initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
                         autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
-                        screenBrightnessThresholds, logicalDisplay, context);
+                        screenBrightnessThresholds, logicalDisplay, context, mHbmController);
             } else {
                 mUseSoftwareAutoBrightnessConfig = false;
             }
@@ -1081,6 +1087,8 @@
             mBrightnessTracker.setBrightnessConfiguration(mBrightnessConfiguration);
         }
 
+        boolean updateScreenBrightnessSetting = false;
+
         // Apply auto-brightness.
         boolean slowChange = false;
         if (Float.isNaN(brightnessState)) {
@@ -1097,11 +1105,7 @@
                 if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
                     slowChange = true; // slowly adapt to auto-brightness
                 }
-                // Tell the rest of the system about the new brightness. Note that we do this
-                // before applying the low power or dim transformations so that the slider
-                // accurately represents the full possible range, even if they range changes what
-                // it means in absolute terms.
-                putScreenBrightnessSetting(brightnessState);
+                updateScreenBrightnessSetting = true;
                 mAppliedAutoBrightness = true;
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
             } else {
@@ -1119,6 +1123,7 @@
             mAppliedAutoBrightness = false;
             brightnessAdjustmentFlags = 0;
         }
+
         // Use default brightness when dozing unless overridden.
         if ((Float.isNaN(brightnessState))
                 && Display.isDozeState(state)) {
@@ -1129,9 +1134,24 @@
         // Apply manual brightness.
         if (Float.isNaN(brightnessState)) {
             brightnessState = clampScreenBrightness(mCurrentScreenBrightnessSetting);
+            if (brightnessState != mCurrentScreenBrightnessSetting) {
+                // The manually chosen screen brightness is outside of the currently allowed
+                // range (i.e., high-brightness-mode), make sure we tell the rest of the system
+                // by updating the setting.
+                updateScreenBrightnessSetting = true;
+            }
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
+        if (updateScreenBrightnessSetting) {
+            // Tell the rest of the system about the new brightness in case we had to change it
+            // for things like auto-brightness or high-brightness-mode. Note that we do this
+            // before applying the low power or dim transformations so that the slider
+            // accurately represents the full possible range, even if they range changes what
+            // it means in absolute terms.
+            putScreenBrightnessSetting(brightnessState);
+        }
+
         // Apply dimming by at least some minimum amount when user activity
         // timeout is about to expire.
         if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1209,9 +1229,10 @@
             // animate to. To avoid this, we check the value first.
             // If the brightnessState is off (-1.0f) we still want to animate to the minimum
             // brightness (0.0f) to accommodate for LED displays, which can appear bright to the
-            // user even when the display is all black.
-            float animateValue = brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT
-                    ? PowerManager.BRIGHTNESS_MIN : brightnessState;
+            // user even when the display is all black. We also clamp here in case some
+            // transformations to the brightness have pushed it outside of the currently
+            // allowed range.
+            float animateValue = clampScreenBrightness(brightnessState);
             final float currentBrightness = mPowerState.getScreenBrightness();
             if (isValidBrightnessValue(animateValue)
                     && !BrightnessSynchronizer.floatEquals(animateValue, currentBrightness)) {
@@ -1354,6 +1375,15 @@
         msg.sendToTarget();
     }
 
+    private HighBrightnessModeController createHbmController() {
+        final DisplayDeviceConfig ddConfig =
+                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
+        final DisplayDeviceConfig.HighBrightnessModeData hbmData =
+                ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
+        return new HighBrightnessModeController(mHandler, PowerManager.BRIGHTNESS_MIN,
+                PowerManager.BRIGHTNESS_MAX, hbmData, () -> sendUpdatePowerStateLocked());
+    }
+
     private void blockScreenOn() {
         if (mPendingScreenOnUnblocker == null) {
             Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
@@ -1469,10 +1499,10 @@
 
     private float clampScreenBrightness(float value) {
         if (Float.isNaN(value)) {
-            return PowerManager.BRIGHTNESS_MIN;
+            value = PowerManager.BRIGHTNESS_MIN;
         }
-        return MathUtils.constrain(
-                value, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX);
+        return MathUtils.constrain(value,
+                mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
     }
 
     // Checks whether the brightness is within the valid brightness range, not including the off or
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 12b810f..2e5561d 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -37,7 +37,7 @@
 class HighBrightnessModeController {
     private static final String TAG = "HighBrightnessModeController";
 
-    private static final boolean DEBUG_HBM = false;
+    private static final boolean DEBUG = false;
 
     private final float mBrightnessMin;
     private final float mBrightnessMax;
@@ -48,6 +48,7 @@
 
     private boolean mIsInAllowedAmbientRange = false;
     private boolean mIsTimeAvailable = false;
+    private boolean mIsAutoBrightnessEnabled = false;
     private float mAutoBrightness;
 
     /**
@@ -84,6 +85,17 @@
         };
     }
 
+    void setAutoBrightnessEnabled(boolean isEnabled) {
+        if (isEnabled == mIsAutoBrightnessEnabled) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "setAutoBrightness( " + isEnabled + " )");
+        }
+        mIsAutoBrightnessEnabled = isEnabled;
+        mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
+    }
+
     float getCurrentBrightnessMin() {
         return mBrightnessMin;
     }
@@ -102,7 +114,7 @@
     }
 
     void onAmbientLuxChange(float ambientLux) {
-        if (!deviceSupportsHbm()) {
+        if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
             return;
         }
 
@@ -132,7 +144,7 @@
                 mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime));
                 mRunningStartTimeMillis = -1;
 
-                if (DEBUG_HBM) {
+                if (DEBUG) {
                     Slog.d(TAG, "New HBM event: " + mEvents.getFirst());
                 }
             }
@@ -142,7 +154,7 @@
     }
 
     private boolean isCurrentlyAllowed() {
-        return mIsTimeAvailable && mIsInAllowedAmbientRange;
+        return mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange;
     }
 
     private boolean deviceSupportsHbm() {
@@ -167,7 +179,7 @@
             timeAlreadyUsed = currentTime - mRunningStartTimeMillis;
         }
 
-        if (DEBUG_HBM) {
+        if (DEBUG) {
             Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed);
         }
 
@@ -187,7 +199,7 @@
             timeAlreadyUsed += event.endTimeMillis - startTimeMillis;
         }
 
-        if (DEBUG_HBM) {
+        if (DEBUG) {
             Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed);
         }
 
@@ -220,7 +232,7 @@
             nextTimeout = timeWhenMinIsGainedBack;
         }
 
-        if (DEBUG_HBM) {
+        if (DEBUG) {
             Slog.d(TAG, "HBM recalculated.  IsAllowedWithoutRestrictions: "
                     + isAllowedWithoutRestrictions
                     + ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
diff --git a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
index 947ee24..f6828d1 100644
--- a/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceSelectAction.java
@@ -100,18 +100,22 @@
         // Wake-up on <Set Stream Path> was not mandatory before CEC 2.0.
         // The message is re-sent at the end of the action for devices that don't support 2.0.
         sendSetStreamPath();
-        int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
-        HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(
-                getTargetAddress());
-        if (targetDevice != null) {
-            targetPowerStatus = targetDevice.getDevicePowerStatus();
-        }
 
-        if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+        if (!mIsCec20) {
             queryDevicePowerStatus();
-        } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
-            finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-            return true;
+        } else {
+            int targetPowerStatus = HdmiControlManager.POWER_STATUS_UNKNOWN;
+            HdmiDeviceInfo targetDevice = localDevice().mService.getHdmiCecNetwork()
+                    .getCecDeviceInfo(getTargetAddress());
+            if (targetDevice != null) {
+                targetPowerStatus = targetDevice.getDevicePowerStatus();
+            }
+            if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+                queryDevicePowerStatus();
+            } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
+                finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+                return true;
+            }
         }
         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
         addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 9d2db94..979e7a4 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -89,7 +89,7 @@
         mSource = source();
         sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
 
-        boolean targetOnBefore = getTargetDevicePowerStatus(mSource, mTargetAddress,
+        boolean is20TargetOnBefore = mIsCec20 && getTargetDevicePowerStatus(mSource, mTargetAddress,
                 HdmiControlManager.POWER_STATUS_UNKNOWN) == HdmiControlManager.POWER_STATUS_ON;
         broadcastActiveSource();
         // If the device is not an audio system itself, request the connected audio system to
@@ -98,18 +98,23 @@
             sendCommand(HdmiCecMessageBuilder.buildSystemAudioModeRequest(getSourceAddress(),
                     Constants.ADDR_AUDIO_SYSTEM, getSourcePath(), true));
         }
-        int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress,
-                HdmiControlManager.POWER_STATUS_UNKNOWN);
-        if (!mIsCec20 || targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+
+        if (!mIsCec20) {
             queryDevicePowerStatus();
-        } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
-            if (!targetOnBefore) {
-                // Suppress 2nd <Active Source> message if the target device was already on when
-                // the 1st one was sent.
-                broadcastActiveSource();
+        } else {
+            int targetPowerStatus = getTargetDevicePowerStatus(mSource, mTargetAddress,
+                    HdmiControlManager.POWER_STATUS_UNKNOWN);
+            if (targetPowerStatus == HdmiControlManager.POWER_STATUS_UNKNOWN) {
+                queryDevicePowerStatus();
+            } else if (targetPowerStatus == HdmiControlManager.POWER_STATUS_ON) {
+                if (!is20TargetOnBefore) {
+                    // Suppress 2nd <Active Source> message if the target device was already on when
+                    // the 1st one was sent.
+                    broadcastActiveSource();
+                }
+                finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+                return true;
             }
-            finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
-            return true;
         }
         mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
         addTimer(mState, HdmiConfig.TIMEOUT_MS);
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index dde45c4..c44089b 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -17,13 +17,16 @@
 package com.android.server.location.contexthub;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
+import android.hardware.SensorPrivacyManagerInternal;
 import android.hardware.contexthub.V1_0.AsyncEventType;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
@@ -59,6 +62,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.DumpUtils;
+import com.android.server.LocalServices;
 import com.android.server.location.ContextHubServiceProto;
 
 import java.io.FileDescriptor;
@@ -127,6 +131,8 @@
     // Lock object for sendWifiSettingUpdate()
     private final Object mSendWifiSettingUpdateLock = new Object();
 
+    private final SensorPrivacyManagerInternal mSensorPrivacyManagerInternal;
+
     /**
      * Class extending the callback to register with a Context Hub.
      */
@@ -186,6 +192,7 @@
         if (mContextHubWrapper == null) {
             mTransactionManager = null;
             mClientManager = null;
+            mSensorPrivacyManagerInternal = null;
             mDefaultClientMap = Collections.emptyMap();
             mContextHubIdToInfoMap = Collections.emptyMap();
             mSupportedContextHubPerms = Collections.emptyList();
@@ -208,6 +215,8 @@
         mClientManager = new ContextHubClientManager(mContext, mContextHubWrapper);
         mTransactionManager = new ContextHubTransactionManager(
                 mContextHubWrapper.getHub(), mClientManager, mNanoAppStateManager);
+        mSensorPrivacyManagerInternal =
+                LocalServices.getService(SensorPrivacyManagerInternal.class);
 
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
         for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
@@ -284,18 +293,16 @@
         }
 
         if (mContextHubWrapper.supportsMicrophoneDisableSettingNotifications()) {
-            sendMicrophoneDisableSettingUpdate();
+            sendMicrophoneDisableSettingUpdateForCurrentUser();
 
-            SensorPrivacyManager.OnSensorPrivacyChangedListener listener =
-                    new SensorPrivacyManager.OnSensorPrivacyChangedListener() {
-                    @Override
-                    public void onSensorPrivacyChanged(boolean enabled) {
-                        sendMicrophoneDisableSettingUpdate();
-                    }
-                };
-            SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
-            manager.addSensorPrivacyListener(
-                    SensorPrivacyManager.Sensors.MICROPHONE, listener);
+            mSensorPrivacyManagerInternal.addSensorPrivacyListenerForAllUsers(
+                    SensorPrivacyManager.Sensors.MICROPHONE, (userId, enabled) -> {
+                        if (userId == getCurrentUserId()) {
+                            Log.d(TAG, "User: " + userId + " enabled: " + enabled);
+                            sendMicrophoneDisableSettingUpdate(enabled);
+                        }
+                });
+
         }
     }
 
@@ -1074,19 +1081,48 @@
     }
 
     /**
-     * Obtains the latest microphone disable setting value and notifies the
-     * Context Hub.
+     * Notifies a microphone disable settings change to the Context Hub.
      */
-    private void sendMicrophoneDisableSettingUpdate() {
-        SensorPrivacyManager manager = SensorPrivacyManager.getInstance(mContext);
-        boolean disabled = manager.isSensorPrivacyEnabled(
-                SensorPrivacyManager.Sensors.MICROPHONE);
-        Log.d(TAG, "Mic Disabled Setting: " + disabled);
-        mContextHubWrapper.onMicrophoneDisableSettingChanged(disabled);
+    private void sendMicrophoneDisableSettingUpdate(boolean enabled) {
+        Log.d(TAG, "Mic Disabled Setting: " + enabled);
+        mContextHubWrapper.onMicrophoneDisableSettingChanged(enabled);
+    }
+
+    /**
+     * Obtains the latest microphone disabled setting for the current user
+     * and notifies the Context Hub.
+     */
+    private void sendMicrophoneDisableSettingUpdateForCurrentUser() {
+        boolean isEnabled = mSensorPrivacyManagerInternal.isSensorPrivacyEnabled(
+                getCurrentUserId(), SensorPrivacyManager.Sensors.MICROPHONE);
+        sendMicrophoneDisableSettingUpdate(isEnabled);
     }
 
 
     private String getCallingPackageName() {
         return mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
     }
+
+    private int getCurrentUserId() {
+        final long id = Binder.clearCallingIdentity();
+        try {
+            UserInfo currentUser = ActivityManager.getService().getCurrentUser();
+            return currentUser.id;
+        } catch (RemoteException e) {
+            // Activity manager not running, nothing we can do - assume user 0.
+        } finally {
+            Binder.restoreCallingIdentity(id);
+        }
+        return UserHandle.USER_SYSTEM;
+    }
+
+    /**
+     * Send a microphone disable settings update whenever the foreground user changes.
+     * We always send a settings update regardless of the previous state for the same user
+     * since the CHRE framework is expected to handle repeated identical setting update.
+     */
+    public void onUserChanged() {
+        Log.d(TAG, "User changed to id: " + getCurrentUserId());
+        sendMicrophoneDisableSettingUpdateForCurrentUser();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e3ccb75..27bf8a13 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -737,10 +737,12 @@
             }
         }
         if (locationExtraPackageNames != null) {
-            // Also grant location permission to location extra packages.
+            // Also grant location and activity recognition permission to location extra packages.
             for (String packageName : locationExtraPackageNames) {
                 grantPermissionsToSystemPackage(pm, packageName, userId,
                         ALWAYS_LOCATION_PERMISSIONS);
+                grantSystemFixedPermissionsToSystemPackage(pm, packageName, userId,
+                        ACTIVITY_RECOGNITION_PERMISSIONS);
             }
         }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ffea6a7..1e49071 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2235,9 +2235,9 @@
 
     /** {@inheritDoc} */
     @Override
-    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
-            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
-            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+    public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+            int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+            int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
         if (!SHOW_SPLASH_SCREENS) {
             return null;
         }
@@ -2264,10 +2264,12 @@
 
             if (theme != context.getThemeResId() || labelRes != 0) {
                 try {
-                    context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
+                    context = context.createPackageContextAsUser(packageName, CONTEXT_RESTRICTED,
+                            UserHandle.of(userId));
                     context.setTheme(theme);
                 } catch (PackageManager.NameNotFoundException e) {
-                    // Ignore
+                    Slog.w(TAG, "Failed creating package context with package name "
+                            + packageName + " for user " + userId, e);
                 }
             }
 
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 0735977b..d512edf 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -714,9 +714,9 @@
      * @return The starting surface.
      *
      */
-    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
-            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
-            int logo, int windowFlags, Configuration overrideConfig, int displayId);
+    StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+            int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+            int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId);
 
     /**
      * Set or clear a window which can behave as the keyguard.
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index a95628f..44f14b4 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -258,8 +258,12 @@
         }
     }
 
+    /**
+     * @deprecated Notify occlude status change via remote animation.
+     */
+    @Deprecated
     public void setOccluded(boolean isOccluded, boolean animate) {
-        if (mKeyguardService != null) {
+        if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
             if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
             mKeyguardService.setOccluded(isOccluded, animate);
         }
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index a1d2f8a..3ae67ae 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -3298,7 +3298,11 @@
                     values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
                             sessionToken.toString());
 
-                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+                    try{
+                        mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+                    }catch(IllegalArgumentException ex){
+                        Slog.w(TAG, "error in insert db for MSG_LOG_WATCH_START", ex);
+                    }
                     args.recycle();
                     break;
                 }
@@ -3313,7 +3317,11 @@
                     values.put(TvContract.WatchedPrograms.COLUMN_INTERNAL_SESSION_TOKEN,
                             sessionToken.toString());
 
-                    mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+                    try{
+                        mContentResolver.insert(TvContract.WatchedPrograms.CONTENT_URI, values);
+                    }catch(IllegalArgumentException ex){
+                        Slog.w(TAG, "error in insert db for MSG_LOG_WATCH_END", ex);
+                    }
                     args.recycle();
                     break;
                 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 3893267..b90408f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -16,6 +16,7 @@
 
 package com.android.server.vibrator;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.vibrator.IVibratorManager;
 import android.os.CombinedVibrationEffect;
@@ -35,9 +36,9 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.util.FrameworkStatsLog;
 
-import com.google.android.collect.Lists;
-
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 import java.util.PriorityQueue;
 
@@ -47,11 +48,16 @@
     private static final boolean DEBUG = false;
 
     /**
-     * Extra timeout added to the end of each synced vibration step as a timeout for the callback
-     * wait, to ensure it finishes even when callbacks from individual vibrators are lost.
+     * Extra timeout added to the end of each vibration step to ensure it finishes even when
+     * vibrator callbacks are lost.
      */
     private static final long CALLBACKS_EXTRA_TIMEOUT = 100;
 
+    /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
+    private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
+
+    private static final List<Step> EMPTY_STEP_LIST = new ArrayList<>();
+
     /** Callbacks for playing a {@link Vibration}. */
     interface VibrationCallbacks {
 
@@ -83,13 +89,10 @@
     private final IBatteryStats mBatteryStatsService;
     private final Vibration mVibration;
     private final VibrationCallbacks mCallbacks;
-    private final SparseArray<VibratorController> mVibrators;
+    private final SparseArray<VibratorController> mVibrators = new SparseArray<>();
+    private final StepQueue mStepQueue = new StepQueue();
 
-    @GuardedBy("mLock")
-    @Nullable
-    private VibrateStep mCurrentVibrateStep;
-    @GuardedBy("mLock")
-    private boolean mForceStop;
+    private volatile boolean mForceStop;
 
     VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators,
             PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService,
@@ -102,7 +105,6 @@
         mBatteryStatsService = batteryStatsService;
 
         CombinedVibrationEffect effect = vib.getEffect();
-        mVibrators = new SparseArray<>();
         for (int i = 0; i < availableVibrators.size(); i++) {
             if (effect.hasVibrator(availableVibrators.keyAt(i))) {
                 mVibrators.put(availableVibrators.keyAt(i), availableVibrators.valueAt(i));
@@ -110,6 +112,15 @@
         }
     }
 
+    Vibration getVibration() {
+        return mVibration;
+    }
+
+    @VisibleForTesting
+    SparseArray<VibratorController> getVibrators() {
+        return mVibrators;
+    }
+
     @Override
     public void binderDied() {
         if (DEBUG) {
@@ -136,8 +147,11 @@
 
     /** Cancel current vibration and shuts down the thread gracefully. */
     public void cancel() {
+        mForceStop = true;
         synchronized (mLock) {
-            mForceStop = true;
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration cancelled");
+            }
             mLock.notify();
         }
     }
@@ -148,11 +162,10 @@
             if (DEBUG) {
                 Slog.d(TAG, "Synced vibration complete reported by vibrator manager");
             }
-            if (mCurrentVibrateStep != null) {
-                for (int i = 0; i < mVibrators.size(); i++) {
-                    mCurrentVibrateStep.vibratorComplete(mVibrators.keyAt(i));
-                }
+            for (int i = 0; i < mVibrators.size(); i++) {
+                mStepQueue.consumeOnVibratorComplete(mVibrators.keyAt(i));
             }
+            mLock.notify();
         }
     }
 
@@ -162,120 +175,73 @@
             if (DEBUG) {
                 Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId);
             }
-            if (mCurrentVibrateStep != null) {
-                mCurrentVibrateStep.vibratorComplete(vibratorId);
-            }
+            mStepQueue.consumeOnVibratorComplete(vibratorId);
+            mLock.notify();
         }
     }
 
-    Vibration getVibration() {
-        return mVibration;
-    }
-
-    @VisibleForTesting
-    SparseArray<VibratorController> getVibrators() {
-        return mVibrators;
-    }
-
     private Vibration.Status playVibration() {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
         try {
-            List<Step> steps = generateSteps(mVibration.getEffect());
-            if (steps.isEmpty()) {
-                // No vibrator matching any incoming vibration effect.
-                return Vibration.Status.IGNORED;
-            }
-            Vibration.Status status = Vibration.Status.FINISHED;
-            final int stepCount = steps.size();
-            for (int i = 0; i < stepCount; i++) {
-                Step step = steps.get(i);
-                synchronized (mLock) {
-                    if (step instanceof VibrateStep) {
-                        mCurrentVibrateStep = (VibrateStep) step;
+            CombinedVibrationEffect.Sequential effect = toSequential(mVibration.getEffect());
+            int stepsPlayed = 0;
+
+            synchronized (mLock) {
+                mStepQueue.offer(new StartVibrateStep(effect));
+                Step topOfQueue;
+
+                while ((topOfQueue = mStepQueue.peek()) != null) {
+                    long waitTime = topOfQueue.calculateWaitTime();
+                    if (waitTime <= 0) {
+                        stepsPlayed += mStepQueue.consume();
                     } else {
-                        mCurrentVibrateStep = null;
+                        try {
+                            mLock.wait(waitTime);
+                        } catch (InterruptedException e) { }
+                    }
+                    if (mForceStop) {
+                        mStepQueue.cancel();
+                        return Vibration.Status.CANCELLED;
                     }
                 }
-                status = step.play();
-                if (status != Vibration.Status.FINISHED) {
-                    // This step was ignored by the vibrators, probably effects were unsupported.
-                    break;
-                }
-                if (mForceStop) {
-                    break;
-                }
             }
-            if (mForceStop) {
-                return Vibration.Status.CANCELLED;
-            }
-            return status;
+
+            // Some effects might be ignored because the specified vibrator don't exist or doesn't
+            // support the effect. We only report ignored here if nothing was played besides the
+            // StartVibrateStep (which means every attempt to turn on the vibrator was ignored).
+            return stepsPlayed > effect.getEffects().size()
+                    ? Vibration.Status.FINISHED : Vibration.Status.IGNORED_UNSUPPORTED;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
-    private List<Step> generateSteps(CombinedVibrationEffect effect) {
-        if (effect instanceof CombinedVibrationEffect.Sequential) {
-            CombinedVibrationEffect.Sequential sequential =
-                    (CombinedVibrationEffect.Sequential) effect;
-            List<Step> steps = new ArrayList<>();
-            final int sequentialEffectCount = sequential.getEffects().size();
-            for (int i = 0; i < sequentialEffectCount; i++) {
-                int delay = sequential.getDelays().get(i);
-                if (delay > 0) {
-                    steps.add(new DelayStep(delay));
-                }
-                steps.addAll(generateSteps(sequential.getEffects().get(i)));
+    private void noteVibratorOn(long duration) {
+        try {
+            if (duration <= 0) {
+                return;
             }
-            final int stepCount = steps.size();
-            for (int i = 0; i < stepCount; i++) {
-                if (steps.get(i) instanceof VibrateStep) {
-                    return steps;
-                }
+            if (duration == Long.MAX_VALUE) {
+                // Repeating duration has started. Report a fixed duration here, noteVibratorOff
+                // should be called when this is cancelled.
+                duration = BATTERY_STATS_REPEATING_VIBRATION_DURATION;
             }
-            // No valid vibrate step was generated, ignore effect completely.
-            return Lists.newArrayList();
+            mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
+            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
+                    duration);
+        } catch (RemoteException e) {
         }
-        VibrateStep vibrateStep = null;
-        if (effect instanceof CombinedVibrationEffect.Mono) {
-            vibrateStep = createVibrateStep(mapToAvailableVibrators(
-                    ((CombinedVibrationEffect.Mono) effect).getEffect()));
-        } else if (effect instanceof CombinedVibrationEffect.Stereo) {
-            vibrateStep = createVibrateStep(filterByAvailableVibrators(
-                    ((CombinedVibrationEffect.Stereo) effect).getEffects()));
-        }
-        return vibrateStep == null ? Lists.newArrayList() : Lists.newArrayList(vibrateStep);
     }
 
-    @Nullable
-    private VibrateStep createVibrateStep(SparseArray<VibrationEffect> effects) {
-        if (effects.size() == 0) {
-            return null;
+    private void noteVibratorOff() {
+        try {
+            mBatteryStatsService.noteVibratorOff(mVibration.uid);
+            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
+                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
+                    /* duration= */ 0);
+        } catch (RemoteException e) {
         }
-        if (effects.size() == 1) {
-            // Create simplified step that handles a single vibrator.
-            return new SingleVibrateStep(mVibrators.get(effects.keyAt(0)), effects.valueAt(0));
-        }
-        return new SyncedVibrateStep(effects);
-    }
-
-    private SparseArray<VibrationEffect> mapToAvailableVibrators(VibrationEffect effect) {
-        SparseArray<VibrationEffect> mappedEffects = new SparseArray<>(mVibrators.size());
-        for (int i = 0; i < mVibrators.size(); i++) {
-            mappedEffects.put(mVibrators.keyAt(i), effect);
-        }
-        return mappedEffects;
-    }
-
-    private SparseArray<VibrationEffect> filterByAvailableVibrators(
-            SparseArray<VibrationEffect> effects) {
-        SparseArray<VibrationEffect> filteredEffects = new SparseArray<>();
-        for (int i = 0; i < effects.size(); i++) {
-            if (mVibrators.contains(effects.keyAt(i))) {
-                filteredEffects.put(effects.keyAt(i), effects.valueAt(i));
-            }
-        }
-        return filteredEffects;
     }
 
     /**
@@ -306,341 +272,240 @@
         return timing;
     }
 
-    /**
-     * Sleeps until given {@code wakeUpTime}.
-     *
-     * <p>This stops immediately when {@link #cancel()} is called.
-     *
-     * @return true if waited until wake-up time, false if it was cancelled.
-     */
-    private boolean waitUntil(long wakeUpTime) {
-        synchronized (mLock) {
-            long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
-            while (durationRemaining > 0) {
-                try {
-                    mLock.wait(durationRemaining);
-                } catch (InterruptedException e) {
-                }
-                if (mForceStop) {
-                    return false;
-                }
-                durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
-            }
+    private static CombinedVibrationEffect.Sequential toSequential(CombinedVibrationEffect effect) {
+        if (effect instanceof CombinedVibrationEffect.Sequential) {
+            return (CombinedVibrationEffect.Sequential) effect;
         }
-        return true;
+        return (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+                .addNext(effect)
+                .combine();
     }
 
-    /**
-     * Sleeps until given {@link VibrateStep#isVibrationComplete()}, or until {@code wakeUpTime}.
-     *
-     * <p>This stops immediately when {@link #cancel()} is called.
-     *
-     * @return true if finished on vibration complete, false if it was cancelled or timed out.
-     */
-    private boolean waitForVibrationComplete(VibrateStep step, long wakeUpTime) {
-        synchronized (mLock) {
-            long durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
-            while (!step.isVibrationComplete() && durationRemaining > 0) {
-                try {
-                    mLock.wait(durationRemaining);
-                } catch (InterruptedException e) {
-                }
-                if (mForceStop) {
-                    return false;
-                }
-                durationRemaining = wakeUpTime - SystemClock.uptimeMillis();
-            }
-        }
-        return step.isVibrationComplete();
-    }
-
-    private void noteVibratorOn(long duration) {
-        try {
-            mBatteryStatsService.noteVibratorOn(mVibration.uid, duration);
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON,
-                    duration);
-        } catch (RemoteException e) {
-        }
-    }
-
-    private void noteVibratorOff() {
-        try {
-            mBatteryStatsService.noteVibratorOff(mVibration.uid);
-            FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED,
-                    mVibration.uid, null, FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__OFF,
-                    /* duration= */ 0);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /** Represent a single synchronized step while playing a {@link CombinedVibrationEffect}. */
-    private interface Step {
-        Vibration.Status play();
-    }
-
-    /** Represent a synchronized vibration step. */
-    private interface VibrateStep extends Step {
-        /** Callback to notify a vibrator has finished playing a effect. */
-        void vibratorComplete(int vibratorId);
-
-        /** Returns true if the vibration played by this step is complete. */
-        boolean isVibrationComplete();
-    }
-
-    /** Represent a vibration on a single vibrator. */
-    private final class SingleVibrateStep implements VibrateStep {
-        private final VibratorController mVibrator;
-        private final VibrationEffect mEffect;
+    /** Queue for {@link Step Steps}, sorted by their start time. */
+    private final class StepQueue {
+        @GuardedBy("mLock")
+        private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
 
         @GuardedBy("mLock")
-        private boolean mVibrationComplete;
-
-        SingleVibrateStep(VibratorController vibrator, VibrationEffect effect) {
-            mVibrator = vibrator;
-            mEffect = effect;
+        public void offer(@NonNull Step step) {
+            mNextSteps.offer(step);
         }
 
         @GuardedBy("mLock")
-        @Override
-        public boolean isVibrationComplete() {
-            return mVibrationComplete;
+        @Nullable
+        public Step peek() {
+            return mNextSteps.peek();
+        }
+
+        /**
+         * Play and remove the step at the top of this queue, and also adds the next steps
+         * generated to be played next.
+         *
+         * @return the number of steps played
+         */
+        @GuardedBy("mLock")
+        public int consume() {
+            Step nextStep = mNextSteps.poll();
+            if (nextStep != null) {
+                mNextSteps.addAll(nextStep.play());
+                return 1;
+            }
+            return 0;
+        }
+
+        /**
+         * Play and remove the step in this queue that should be anticipated by the vibrator
+         * completion callback.
+         *
+         * <p>This assumes only one of the next steps is waiting on this given vibrator, so the
+         * first step found is played by this method, in no particular order.
+         */
+        @GuardedBy("mLock")
+        public void consumeOnVibratorComplete(int vibratorId) {
+            Iterator<Step> it = mNextSteps.iterator();
+            List<Step> nextSteps = EMPTY_STEP_LIST;
+            while (it.hasNext()) {
+                Step step = it.next();
+                if (step.shouldPlayWhenVibratorComplete(vibratorId)) {
+                    it.remove();
+                    nextSteps = step.play();
+                    break;
+                }
+            }
+            mNextSteps.addAll(nextSteps);
+        }
+
+        /**
+         * Cancel the current queue, clearing all remaining steps.
+         *
+         * <p>This will remove and trigger {@link Step#cancel()} in all steps, in order.
+         */
+        @GuardedBy("mLock")
+        public void cancel() {
+            Step step;
+            while ((step = mNextSteps.poll()) != null) {
+                step.cancel();
+            }
+        }
+    }
+
+    /**
+     * Represent a single step for playing a vibration.
+     *
+     * <p>Every step has a start time, which can be used to apply delays between steps while
+     * executing them in sequence.
+     */
+    private abstract class Step implements Comparable<Step> {
+        public final long startTime;
+
+        Step(long startTime) {
+            this.startTime = startTime;
+        }
+
+        /** Play this step, returning a (possibly empty) list of next steps. */
+        @NonNull
+        public abstract List<Step> play();
+
+        /** Cancel this pending step. */
+        public void cancel() {
+        }
+
+        /**
+         * Return true to play this step right after a vibrator has notified vibration completed,
+         * used to anticipate steps waiting on vibrator callbacks with a timeout.
+         */
+        public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+            return false;
+        }
+
+        /** Returns the time in millis to wait before playing this step. */
+        public long calculateWaitTime() {
+            if (startTime == Long.MAX_VALUE) {
+                // This step don't have a predefined start time, it's just marked to be executed
+                // after all other steps have finished.
+                return 0;
+            }
+            return Math.max(0, startTime - SystemClock.uptimeMillis());
         }
 
         @Override
-        public void vibratorComplete(int vibratorId) {
-            if (mVibrator.getVibratorInfo().getId() != vibratorId) {
-                return;
-            }
-            if (mEffect instanceof VibrationEffect.OneShot
-                    || mEffect instanceof VibrationEffect.Waveform) {
-                // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks.
-                return;
-            }
-            mVibrator.off();
-            synchronized (mLock) {
-                mVibrationComplete = true;
-                mLock.notify();
-            }
+        public int compareTo(Step o) {
+            return Long.compare(startTime, o.startTime);
+        }
+    }
+
+    /**
+     * Starts a sync vibration.
+     *
+     * <p>If this step has successfully started playing a vibration on any vibrator, it will always
+     * add a {@link FinishVibrateStep} to the queue, to be played after all vibrators have finished
+     * all their individual steps.
+     *
+     * <o>If this step does not start any vibrator, it will add a {@link StartVibrateStep} if the
+     * sequential effect isn't finished yet.
+     */
+    private final class StartVibrateStep extends Step {
+        public final CombinedVibrationEffect.Sequential sequentialEffect;
+        public final int currentIndex;
+
+        StartVibrateStep(CombinedVibrationEffect.Sequential effect) {
+            this(SystemClock.uptimeMillis() + effect.getDelays().get(0), effect, /* index= */ 0);
+        }
+
+        StartVibrateStep(long startTime, CombinedVibrationEffect.Sequential effect, int index) {
+            super(startTime);
+            sequentialEffect = effect;
+            currentIndex = index;
         }
 
         @Override
-        public Vibration.Status play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SingleVibrateStep");
+        public List<Step> play() {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "StartVibrateStep");
+            List<Step> nextSteps = new ArrayList<>();
             long duration = -1;
             try {
                 if (DEBUG) {
-                    Slog.d(TAG, "SingleVibrateStep starting...");
+                    Slog.d(TAG, "StartVibrateStep for effect #" + currentIndex);
                 }
-                long startTime = SystemClock.uptimeMillis();
-                duration = vibratePredefined(mEffect);
-
-                if (duration > 0) {
-                    noteVibratorOn(duration);
-                    // Vibration is playing with no need to control amplitudes, just wait for native
-                    // callback or timeout.
-                    if (waitForVibrationComplete(this,
-                            startTime + duration + CALLBACKS_EXTRA_TIMEOUT)) {
-                        return Vibration.Status.FINISHED;
-                    }
-                    // Timed out or vibration cancelled. Stop vibrator anyway.
-                    mVibrator.off();
-                    return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
+                CombinedVibrationEffect effect = sequentialEffect.getEffects().get(currentIndex);
+                DeviceEffectMap effectMapping = createEffectToVibratorMapping(effect);
+                if (effectMapping == null) {
+                    // Unable to map effects to vibrators, ignore this step.
+                    return nextSteps;
                 }
 
-                startTime = SystemClock.uptimeMillis();
-                AmplitudeStep amplitudeStep = vibrateWithAmplitude(mEffect, startTime);
-                if (amplitudeStep == null) {
-                    // Vibration could not be played with or without amplitude steps.
-                    return Vibration.Status.IGNORED_UNSUPPORTED;
-                }
-
-                duration = mEffect instanceof VibrationEffect.Prebaked
-                        ? ((VibrationEffect.Prebaked) mEffect).getFallbackEffect().getDuration()
-                        : mEffect.getDuration();
-                if (duration < Long.MAX_VALUE) {
-                    // Only report vibration stats if we know how long we will be vibrating.
-                    noteVibratorOn(duration);
-                }
-                while (amplitudeStep != null) {
-                    if (!waitUntil(amplitudeStep.startTime)) {
-                        mVibrator.off();
-                        return Vibration.Status.CANCELLED;
-                    }
-                    amplitudeStep.play();
-                    amplitudeStep = amplitudeStep.nextStep();
-                }
-
-                return Vibration.Status.FINISHED;
+                duration = startVibrating(effectMapping, nextSteps);
+                noteVibratorOn(duration);
             } finally {
-                if (duration > 0 && duration < Long.MAX_VALUE) {
-                    noteVibratorOff();
-                }
-                if (DEBUG) {
-                    Slog.d(TAG, "SingleVibrateStep done.");
+                // If this step triggered any vibrator then add a finish step to wait for all
+                // active vibrators to finish their individual steps before going to the next.
+                Step nextStep = duration > 0 ? new FinishVibrateStep(this) : nextStep();
+                if (nextStep != null) {
+                    nextSteps.add(nextStep);
                 }
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
+            return nextSteps;
         }
 
         /**
-         * Try to vibrate given effect using prebaked or composed predefined effects.
-         *
-         * @return the duration, in millis, expected for the vibration, or -1 if effect cannot be
-         * played with predefined effects.
+         * Create the next {@link StartVibrateStep} to play this sequential effect, starting at the
+         * time this method is called, or null if sequence is complete.
          */
-        private long vibratePredefined(VibrationEffect effect) {
-            if (effect instanceof VibrationEffect.Prebaked) {
-                VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-                long duration = mVibrator.on(prebaked, mVibration.id);
-                if (duration > 0) {
-                    return duration;
-                }
-                if (prebaked.getFallbackEffect() != null) {
-                    return vibratePredefined(prebaked.getFallbackEffect());
-                }
-            } else if (effect instanceof VibrationEffect.Composed) {
-                VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
-                return mVibrator.on(composed, mVibration.id);
+        @Nullable
+        private Step nextStep() {
+            int nextIndex = currentIndex + 1;
+            if (nextIndex >= sequentialEffect.getEffects().size()) {
+                return null;
             }
-            // OneShot and Waveform effects require amplitude change after calling vibrator.on.
-            return -1;
+            long nextEffectDelay = sequentialEffect.getDelays().get(nextIndex);
+            long nextStartTime = SystemClock.uptimeMillis() + nextEffectDelay;
+            return new StartVibrateStep(nextStartTime, sequentialEffect, nextIndex);
         }
 
-        /**
-         * Try to vibrate given effect using {@link AmplitudeStep} to control vibration amplitude.
-         *
-         * @return the {@link AmplitudeStep} to start this vibration, or {@code null} if vibration
-         * do not require amplitude control.
-         */
-        private AmplitudeStep vibrateWithAmplitude(VibrationEffect effect, long startTime) {
-            int vibratorId = mVibrator.getVibratorInfo().getId();
-            if (effect instanceof VibrationEffect.OneShot) {
-                VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
-                return new AmplitudeStep(vibratorId, oneShot, startTime, startTime);
-            } else if (effect instanceof VibrationEffect.Waveform) {
-                VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
-                return new AmplitudeStep(vibratorId, waveform, startTime, startTime);
-            } else if (effect instanceof VibrationEffect.Prebaked) {
-                VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
-                if (prebaked.getFallbackEffect() != null) {
-                    return vibrateWithAmplitude(prebaked.getFallbackEffect(), startTime);
-                }
+        /** Create a mapping of individual {@link VibrationEffect} to available vibrators. */
+        @Nullable
+        private DeviceEffectMap createEffectToVibratorMapping(
+                CombinedVibrationEffect effect) {
+            if (effect instanceof CombinedVibrationEffect.Mono) {
+                return new DeviceEffectMap((CombinedVibrationEffect.Mono) effect);
+            }
+            if (effect instanceof CombinedVibrationEffect.Stereo) {
+                return new DeviceEffectMap((CombinedVibrationEffect.Stereo) effect);
             }
             return null;
         }
-    }
-
-    /** Represent a synchronized vibration step on multiple vibrators. */
-    private final class SyncedVibrateStep implements VibrateStep {
-        private final SparseArray<VibrationEffect> mEffects;
-        private final long mRequiredCapabilities;
-        private final int[] mVibratorIds;
-
-        @GuardedBy("mLock")
-        private int mActiveVibratorCounter;
-
-        SyncedVibrateStep(SparseArray<VibrationEffect> effects) {
-            mEffects = effects;
-            mActiveVibratorCounter = mEffects.size();
-            mRequiredCapabilities = calculateRequiredSyncCapabilities(effects);
-            mVibratorIds = new int[effects.size()];
-            for (int i = 0; i < effects.size(); i++) {
-                mVibratorIds[i] = effects.keyAt(i);
-            }
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        public boolean isVibrationComplete() {
-            return mActiveVibratorCounter <= 0;
-        }
-
-        @Override
-        public void vibratorComplete(int vibratorId) {
-            VibrationEffect effect = mEffects.get(vibratorId);
-            if (effect == null) {
-                return;
-            }
-            if (effect instanceof VibrationEffect.OneShot
-                    || effect instanceof VibrationEffect.Waveform) {
-                // Oneshot and Waveform are controlled by amplitude steps, ignore callbacks.
-                return;
-            }
-            mVibrators.get(vibratorId).off();
-            synchronized (mLock) {
-                --mActiveVibratorCounter;
-                mLock.notify();
-            }
-        }
-
-        @Override
-        public Vibration.Status play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "SyncedVibrateStep");
-            long duration = -1;
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "SyncedVibrateStep starting...");
-                }
-                final PriorityQueue<AmplitudeStep> nextSteps = new PriorityQueue<>(mEffects.size());
-                long startTime = SystemClock.uptimeMillis();
-                duration = startVibratingSynced(startTime, nextSteps);
-
-                if (duration <= 0) {
-                    // Vibrate step failed, vibrator could not be turned on for this step.
-                    return Vibration.Status.IGNORED;
-                }
-
-                noteVibratorOn(duration);
-                while (!nextSteps.isEmpty()) {
-                    AmplitudeStep step = nextSteps.poll();
-                    if (!waitUntil(step.startTime)) {
-                        stopAllVibrators();
-                        return Vibration.Status.CANCELLED;
-                    }
-                    step.play();
-                    AmplitudeStep nextStep = step.nextStep();
-                    if (nextStep == null) {
-                        // This vibrator has finished playing the effect for this step.
-                        synchronized (mLock) {
-                            mActiveVibratorCounter--;
-                        }
-                    } else {
-                        nextSteps.add(nextStep);
-                    }
-                }
-
-                synchronized (mLock) {
-                    // All OneShot and Waveform effects have finished. Just wait for the other
-                    // effects to end via native callbacks before finishing this synced step.
-                    final long wakeUpTime = startTime + duration + CALLBACKS_EXTRA_TIMEOUT;
-                    if (mActiveVibratorCounter <= 0 || waitForVibrationComplete(this, wakeUpTime)) {
-                        return Vibration.Status.FINISHED;
-                    }
-
-                    // Timed out or vibration cancelled. Stop all vibrators anyway.
-                    stopAllVibrators();
-                    return mForceStop ? Vibration.Status.CANCELLED : Vibration.Status.FINISHED;
-                }
-            } finally {
-                if (duration > 0) {
-                    noteVibratorOff();
-                }
-                if (DEBUG) {
-                    Slog.d(TAG, "SyncedVibrateStep done.");
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
 
         /**
          * Starts playing effects on designated vibrators, in sync.
          *
-         * @return A positive duration, in millis, to wait for the completion of this effect.
-         * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
-         * returns the duration of a single run to be used as timeout for callbacks.
+         * @param effectMapping The {@link CombinedVibrationEffect} mapped to this device vibrators
+         * @param nextSteps     An output list to accumulate the future {@link Step Steps} created
+         *                      by this method, typically one for each vibrator that has
+         *                      successfully started vibrating on this step.
+         * @return The duration, in millis, of the {@link CombinedVibrationEffect}. Repeating
+         * waveforms return {@link Long#MAX_VALUE}. Zero or negative values indicate the vibrators
+         * have ignored all effects.
          */
-        private long startVibratingSynced(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
+        private long startVibrating(DeviceEffectMap effectMapping, List<Step> nextSteps) {
+            int vibratorCount = effectMapping.size();
+            if (vibratorCount == 0) {
+                // No effect was mapped to any available vibrator.
+                return 0;
+            }
+
+            VibratorOnStep[] steps = new VibratorOnStep[vibratorCount];
+            long vibrationStartTime = SystemClock.uptimeMillis();
+            for (int i = 0; i < vibratorCount; i++) {
+                steps[i] = new VibratorOnStep(vibrationStartTime,
+                        mVibrators.get(effectMapping.vibratorIdAt(i)), effectMapping.effectAt(i));
+            }
+
+            if (steps.length == 1) {
+                // No need to prepare and trigger sync effects on a single vibrator.
+                return startVibrating(steps[0], nextSteps);
+            }
+
             // This synchronization of vibrators should be executed one at a time, even if we are
             // vibrating different sets of vibrators in parallel. The manager can only prepareSynced
             // one set of vibrators at a time.
@@ -648,17 +513,24 @@
                 boolean hasPrepared = false;
                 boolean hasTriggered = false;
                 try {
-                    hasPrepared = mCallbacks.prepareSyncedVibration(mRequiredCapabilities,
-                            mVibratorIds);
-                    long timeout = startVibrating(startTime, nextSteps);
+                    hasPrepared = mCallbacks.prepareSyncedVibration(
+                            effectMapping.getRequiredSyncCapabilities(),
+                            effectMapping.getVibratorIds());
 
-                    // Check if preparation was successful, otherwise devices area already vibrating
-                    if (hasPrepared) {
+                    long duration = 0;
+                    for (VibratorOnStep step : steps) {
+                        duration = Math.max(duration, startVibrating(step, nextSteps));
+                    }
+
+                    // Check if sync was prepared and if any step was accepted by a vibrator,
+                    // otherwise there is nothing to trigger here.
+                    if (hasPrepared && duration > 0) {
                         hasTriggered = mCallbacks.triggerSyncedVibration(mVibration.id);
                     }
-                    return timeout;
+                    return duration;
                 } finally {
                     if (hasPrepared && !hasTriggered) {
+                        // Trigger has failed or all steps were ignored by the vibrators.
                         mCallbacks.cancelSyncedVibration();
                         return 0;
                     }
@@ -666,77 +538,365 @@
             }
         }
 
-        /**
-         * Starts playing effects on designated vibrators.
-         *
-         * <p>This includes the {@link VibrationEffect.OneShot} and {@link VibrationEffect.Waveform}
-         * effects, that should start in sync with all other effects in this step. The waveforms are
-         * controlled by {@link AmplitudeStep} added to the {@code nextSteps} queue.
-         *
-         * @return A positive duration, in millis, to wait for the completion of this effect.
-         * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
-         * returns the duration of a single run to be used as timeout for callbacks.
-         */
-        private long startVibrating(long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
-            long maxDuration = 0;
-            for (int i = 0; i < mEffects.size(); i++) {
-                VibratorController controller = mVibrators.get(mEffects.keyAt(i));
-                VibrationEffect effect = mEffects.valueAt(i);
-                maxDuration = Math.max(maxDuration,
-                        startVibrating(controller, effect, startTime, nextSteps));
+        private long startVibrating(VibratorOnStep step, List<Step> nextSteps) {
+            nextSteps.addAll(step.play());
+            return step.getDuration();
+        }
+    }
+
+    /**
+     * Finish a sync vibration started by a {@link StartVibrateStep}.
+     *
+     * <p>This only plays after all active vibrators steps have finished, and adds a {@link
+     * StartVibrateStep} to the queue if the sequential effect isn't finished yet.
+     */
+    private final class FinishVibrateStep extends Step {
+        public final StartVibrateStep startedStep;
+
+        FinishVibrateStep(StartVibrateStep startedStep) {
+            super(Long.MAX_VALUE); // No predefined startTime, just wait for all steps in the queue.
+            this.startedStep = startedStep;
+        }
+
+        @Override
+        public List<Step> play() {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "FinishVibrateStep");
+            try {
+                if (DEBUG) {
+                    Slog.d(TAG, "FinishVibrateStep for effect #" + startedStep.currentIndex);
+                }
+                noteVibratorOff();
+                Step nextStep = startedStep.nextStep();
+                return nextStep == null ? EMPTY_STEP_LIST : Arrays.asList(nextStep);
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
-            return maxDuration;
+        }
+
+        @Override
+        public void cancel() {
+            noteVibratorOff();
+        }
+    }
+
+    /**
+     * Represent a step turn the vibrator on.
+     *
+     * <p>No other calls to the vibrator is made from this step, so this can be played in between
+     * calls to 'prepare' and 'trigger' for synchronized vibrations.
+     */
+    private final class VibratorOnStep extends Step {
+        public final VibratorController controller;
+        public final VibrationEffect effect;
+        private long mDuration;
+
+        VibratorOnStep(long startTime, VibratorController controller, VibrationEffect effect) {
+            super(startTime);
+            this.controller = controller;
+            this.effect = effect;
         }
 
         /**
-         * Play a single effect on a single vibrator.
-         *
-         * @return A positive duration, in millis, to wait for the completion of this effect.
-         * Non-positive values indicate the vibrator has ignored this effect. Repeating waveform
-         * returns the duration of a single run to be used as timeout for callbacks.
+         * Return the duration, in millis, of this effect. Repeating waveforms return {@link
+         * Long#MAX_VALUE}. Zero or negative values indicate the vibrator has ignored this effect.
          */
-        private long startVibrating(VibratorController controller, VibrationEffect effect,
-                long startTime, PriorityQueue<AmplitudeStep> nextSteps) {
-            int vibratorId = controller.getVibratorInfo().getId();
-            long duration;
+        public long getDuration() {
+            return mDuration;
+        }
+
+        @Override
+        public List<Step> play() {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOnStep");
+            try {
+                if (DEBUG) {
+                    Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId());
+                }
+                List<Step> nextSteps = new ArrayList<>();
+                mDuration = startVibrating(effect, nextSteps);
+                return nextSteps;
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        private long startVibrating(VibrationEffect effect, List<Step> nextSteps) {
+            final long duration;
+            final long now = SystemClock.uptimeMillis();
             if (effect instanceof VibrationEffect.OneShot) {
                 VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) effect;
                 duration = oneShot.getDuration();
+                // Do NOT set amplitude here. This might be called between prepareSynced and
+                // triggerSynced, so the vibrator is not actually turned on here.
+                // The next steps will handle the amplitude after the vibrator has turned on.
                 controller.on(duration, mVibration.id);
-                nextSteps.add(
-                        new AmplitudeStep(vibratorId, oneShot, startTime, startTime + duration));
+                nextSteps.add(new VibratorAmplitudeStep(now, controller, oneShot,
+                        now + duration + CALLBACKS_EXTRA_TIMEOUT));
             } else if (effect instanceof VibrationEffect.Waveform) {
                 VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) effect;
-                duration = getVibratorOnDuration(waveform, 0);
-                if (duration > 0) {
-                    // Waveform starts by turning vibrator on. Do it in this sync vibrate step.
-                    controller.on(duration, mVibration.id);
+                // Return the full duration of this waveform effect.
+                duration = waveform.getDuration();
+                long onDuration = getVibratorOnDuration(waveform, 0);
+                if (onDuration > 0) {
+                    // Do NOT set amplitude here. This might be called between prepareSynced and
+                    // triggerSynced, so the vibrator is not actually turned on here.
+                    // The next steps will handle the amplitudes after the vibrator has turned on.
+                    controller.on(onDuration, mVibration.id);
                 }
-                nextSteps.add(
-                        new AmplitudeStep(vibratorId, waveform, startTime, startTime + duration));
+                long offTime = onDuration > 0 ? now + onDuration + CALLBACKS_EXTRA_TIMEOUT : now;
+                nextSteps.add(new VibratorAmplitudeStep(now, controller, waveform, offTime));
             } else if (effect instanceof VibrationEffect.Prebaked) {
                 VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
                 duration = controller.on(prebaked, mVibration.id);
-                if (duration <= 0 && prebaked.getFallbackEffect() != null) {
-                    return startVibrating(controller, prebaked.getFallbackEffect(), startTime,
-                            nextSteps);
+                if (duration > 0) {
+                    nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
+                            controller));
+                } else if (prebaked.getFallbackEffect() != null) {
+                    return startVibrating(prebaked.getFallbackEffect(), nextSteps);
                 }
             } else if (effect instanceof VibrationEffect.Composed) {
                 VibrationEffect.Composed composed = (VibrationEffect.Composed) effect;
                 duration = controller.on(composed, mVibration.id);
+                if (duration > 0) {
+                    nextSteps.add(new VibratorOffStep(now + duration + CALLBACKS_EXTRA_TIMEOUT,
+                            controller));
+                }
             } else {
                 duration = 0;
             }
             return duration;
         }
+    }
 
-        private void stopAllVibrators() {
-            for (int vibratorId : mVibratorIds) {
-                VibratorController controller = mVibrators.get(vibratorId);
-                if (controller != null) {
-                    controller.off();
+    /**
+     * Represents a step to turn the vibrator off.
+     *
+     * <p>This runs after a timeout on the expected time the vibrator should have finished playing,
+     * and can anticipated by vibrator complete callbacks.
+     */
+    private final class VibratorOffStep extends Step {
+        public final VibratorController controller;
+
+        VibratorOffStep(long startTime, VibratorController controller) {
+            super(startTime);
+            this.controller = controller;
+        }
+
+        @Override
+        public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+            return controller.getVibratorInfo().getId() == vibratorId;
+        }
+
+        @Override
+        public List<Step> play() {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorOffStep");
+            try {
+                stopVibrating();
+                return EMPTY_STEP_LIST;
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            stopVibrating();
+        }
+
+        private void stopVibrating() {
+            if (DEBUG) {
+                Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
+            }
+            controller.off();
+        }
+    }
+
+    /** Represents a step to change the amplitude of the vibrator. */
+    private final class VibratorAmplitudeStep extends Step {
+        public final VibratorController controller;
+        public final VibrationEffect.Waveform waveform;
+        public final int currentIndex;
+        public final long expectedVibratorStopTime;
+
+        private long mNextVibratorStopTime;
+
+        VibratorAmplitudeStep(long startTime, VibratorController controller,
+                VibrationEffect.OneShot oneShot, long expectedVibratorStopTime) {
+            this(startTime, controller,
+                    (VibrationEffect.Waveform) VibrationEffect.createWaveform(
+                            new long[]{oneShot.getDuration()}, new int[]{oneShot.getAmplitude()},
+                            /* repeat= */ -1),
+                    expectedVibratorStopTime);
+        }
+
+        VibratorAmplitudeStep(long startTime, VibratorController controller,
+                VibrationEffect.Waveform waveform, long expectedVibratorStopTime) {
+            this(startTime, controller, waveform, /* index= */ 0, expectedVibratorStopTime);
+        }
+
+        VibratorAmplitudeStep(long startTime, VibratorController controller,
+                VibrationEffect.Waveform waveform, int index, long expectedVibratorStopTime) {
+            super(startTime);
+            this.controller = controller;
+            this.waveform = waveform;
+            this.currentIndex = index;
+            this.expectedVibratorStopTime = expectedVibratorStopTime;
+            mNextVibratorStopTime = expectedVibratorStopTime;
+        }
+
+        @Override
+        public boolean shouldPlayWhenVibratorComplete(int vibratorId) {
+            if (controller.getVibratorInfo().getId() == vibratorId) {
+                mNextVibratorStopTime = SystemClock.uptimeMillis();
+            }
+            return false;
+        }
+
+        @Override
+        public List<Step> play() {
+            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorAmplitudeStep");
+            try {
+                if (DEBUG) {
+                    long latency = SystemClock.uptimeMillis() - startTime;
+                    Slog.d(TAG, "Running amplitude step with " + latency + "ms latency.");
+                }
+                if (waveform.getTimings()[currentIndex] == 0) {
+                    // Skip waveform entries with zero timing.
+                    return nextSteps();
+                }
+                int amplitude = waveform.getAmplitudes()[currentIndex];
+                if (amplitude == 0) {
+                    stopVibrating();
+                    return nextSteps();
+                }
+                if (startTime >= mNextVibratorStopTime) {
+                    // Vibrator has stopped. Turn vibrator back on for the duration of another
+                    // cycle before setting the amplitude.
+                    long onDuration = getVibratorOnDuration(waveform, currentIndex);
+                    if (onDuration > 0) {
+                        startVibrating(onDuration);
+                        mNextVibratorStopTime =
+                                SystemClock.uptimeMillis() + onDuration + CALLBACKS_EXTRA_TIMEOUT;
+                    }
+                }
+                changeAmplitude(amplitude);
+                return nextSteps();
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        @Override
+        public void cancel() {
+            stopVibrating();
+        }
+
+        private void stopVibrating() {
+            if (DEBUG) {
+                Slog.d(TAG, "Turning off vibrator " + controller.getVibratorInfo().getId());
+            }
+            controller.off();
+            mNextVibratorStopTime = SystemClock.uptimeMillis();
+        }
+
+        private void startVibrating(long duration) {
+            if (DEBUG) {
+                Slog.d(TAG, "Turning on vibrator " + controller.getVibratorInfo().getId() + " for "
+                        + duration + "ms");
+            }
+            controller.on(duration, mVibration.id);
+        }
+
+        private void changeAmplitude(int amplitude) {
+            if (DEBUG) {
+                Slog.d(TAG, "Amplitude changed on vibrator " + controller.getVibratorInfo().getId()
+                        + " to " + amplitude);
+            }
+            controller.setAmplitude(amplitude);
+        }
+
+        @NonNull
+        private List<Step> nextSteps() {
+            long nextStartTime = startTime + waveform.getTimings()[currentIndex];
+            int nextIndex = currentIndex + 1;
+            if (nextIndex >= waveform.getTimings().length) {
+                nextIndex = waveform.getRepeatIndex();
+            }
+            if (nextIndex < 0) {
+                return Arrays.asList(new VibratorOffStep(nextStartTime, controller));
+            }
+            return Arrays.asList(new VibratorAmplitudeStep(nextStartTime, controller, waveform,
+                    nextIndex, mNextVibratorStopTime));
+        }
+    }
+
+    /**
+     * Map a {@link CombinedVibrationEffect} to the vibrators available on the device.
+     *
+     * <p>This contains the logic to find the capabilities required from {@link IVibratorManager} to
+     * play all of the effects in sync.
+     */
+    private final class DeviceEffectMap {
+        private final SparseArray<VibrationEffect> mVibratorEffects;
+        private final int[] mVibratorIds;
+        private final long mRequiredSyncCapabilities;
+
+        DeviceEffectMap(CombinedVibrationEffect.Mono mono) {
+            mVibratorEffects = new SparseArray<>(mVibrators.size());
+            mVibratorIds = new int[mVibrators.size()];
+            for (int i = 0; i < mVibrators.size(); i++) {
+                int vibratorId = mVibrators.keyAt(i);
+                mVibratorEffects.put(vibratorId, mono.getEffect());
+                mVibratorIds[i] = vibratorId;
+            }
+            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+        }
+
+        DeviceEffectMap(CombinedVibrationEffect.Stereo stereo) {
+            SparseArray<VibrationEffect> stereoEffects = stereo.getEffects();
+            mVibratorEffects = new SparseArray<>();
+            for (int i = 0; i < stereoEffects.size(); i++) {
+                int vibratorId = stereoEffects.keyAt(i);
+                if (mVibrators.contains(vibratorId)) {
+                    mVibratorEffects.put(vibratorId, stereoEffects.valueAt(i));
                 }
             }
+            mVibratorIds = new int[mVibratorEffects.size()];
+            for (int i = 0; i < mVibratorEffects.size(); i++) {
+                mVibratorIds[i] = mVibratorEffects.keyAt(i);
+            }
+            mRequiredSyncCapabilities = calculateRequiredSyncCapabilities(mVibratorEffects);
+        }
+
+        /**
+         * Return the number of vibrators mapped to play the {@link CombinedVibrationEffect} on this
+         * device.
+         */
+        public int size() {
+            return mVibratorIds.length;
+        }
+
+        /**
+         * Return all capabilities required to play the {@link CombinedVibrationEffect} in
+         * between calls to {@link IVibratorManager#prepareSynced} and
+         * {@link IVibratorManager#triggerSynced}.
+         */
+        public long getRequiredSyncCapabilities() {
+            return mRequiredSyncCapabilities;
+        }
+
+        /** Return all vibrator ids mapped to play the {@link CombinedVibrationEffect}. */
+        public int[] getVibratorIds() {
+            return mVibratorIds;
+        }
+
+        /** Return the id of the vibrator at given index. */
+        public int vibratorIdAt(int index) {
+            return mVibratorEffects.keyAt(index);
+        }
+
+        /** Return the {@link VibrationEffect} at given index. */
+        public VibrationEffect effectAt(int index) {
+            return mVibratorEffects.valueAt(index);
         }
 
         /**
@@ -782,145 +942,4 @@
                     && (prepareCapabilities & ~capability) != 0;
         }
     }
-
-    /** Represent a step to set amplitude on a single vibrator. */
-    private final class AmplitudeStep implements Step, Comparable<AmplitudeStep> {
-        public final int vibratorId;
-        public final VibrationEffect.Waveform waveform;
-        public final int currentIndex;
-        public final long startTime;
-        public final long vibratorStopTime;
-
-        AmplitudeStep(int vibratorId, VibrationEffect.OneShot oneShot,
-                long startTime, long vibratorStopTime) {
-            this(vibratorId, (VibrationEffect.Waveform) VibrationEffect.createWaveform(
-                    new long[]{oneShot.getDuration()},
-                    new int[]{oneShot.getAmplitude()}, /* repeat= */ -1),
-                    startTime,
-                    vibratorStopTime);
-        }
-
-        AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform,
-                long startTime, long vibratorStopTime) {
-            this(vibratorId, waveform, /* index= */ 0, startTime, vibratorStopTime);
-        }
-
-        AmplitudeStep(int vibratorId, VibrationEffect.Waveform waveform,
-                int index, long startTime, long vibratorStopTime) {
-            this.vibratorId = vibratorId;
-            this.waveform = waveform;
-            this.currentIndex = index;
-            this.startTime = startTime;
-            this.vibratorStopTime = vibratorStopTime;
-        }
-
-        @Override
-        public Vibration.Status play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "AmplitudeStep");
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "AmplitudeStep starting on vibrator " + vibratorId + "...");
-                }
-                VibratorController controller = mVibrators.get(vibratorId);
-                if (currentIndex < 0) {
-                    controller.off();
-                    if (DEBUG) {
-                        Slog.d(TAG, "Vibrator turned off and finishing");
-                    }
-                    return Vibration.Status.FINISHED;
-                }
-                if (waveform.getTimings()[currentIndex] == 0) {
-                    // Skip waveform entries with zero timing.
-                    return Vibration.Status.FINISHED;
-                }
-                int amplitude = waveform.getAmplitudes()[currentIndex];
-                if (amplitude == 0) {
-                    controller.off();
-                    if (DEBUG) {
-                        Slog.d(TAG, "Vibrator turned off");
-                    }
-                    return Vibration.Status.FINISHED;
-                }
-                if (startTime >= vibratorStopTime) {
-                    // Vibrator has stopped. Turn vibrator back on for the duration of another
-                    // cycle before setting the amplitude.
-                    long onDuration = getVibratorOnDuration(waveform, currentIndex);
-                    if (onDuration > 0) {
-                        controller.on(onDuration, mVibration.id);
-                        if (DEBUG) {
-                            Slog.d(TAG, "Vibrator turned on for " + onDuration + "ms");
-                        }
-                    }
-                }
-                controller.setAmplitude(amplitude);
-                if (DEBUG) {
-                    Slog.d(TAG, "Amplitude changed to " + amplitude);
-                }
-                return Vibration.Status.FINISHED;
-            } finally {
-                if (DEBUG) {
-                    Slog.d(TAG, "AmplitudeStep done.");
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-
-        @Override
-        public int compareTo(AmplitudeStep o) {
-            return Long.compare(startTime, o.startTime);
-        }
-
-        /** Return next {@link AmplitudeStep} from this waveform, of {@code null} if finished. */
-        @Nullable
-        public AmplitudeStep nextStep() {
-            if (currentIndex < 0) {
-                // Waveform has ended, no more steps to run.
-                return null;
-            }
-            long nextStartTime = startTime + waveform.getTimings()[currentIndex];
-            int nextIndex = currentIndex + 1;
-            if (nextIndex >= waveform.getTimings().length) {
-                nextIndex = waveform.getRepeatIndex();
-            }
-            return new AmplitudeStep(vibratorId, waveform, nextIndex, nextStartTime,
-                    nextVibratorStopTime());
-        }
-
-        /** Return next time the vibrator will stop after this step is played. */
-        private long nextVibratorStopTime() {
-            if (currentIndex < 0 || waveform.getTimings()[currentIndex] == 0
-                    || startTime < vibratorStopTime) {
-                return vibratorStopTime;
-            }
-            return startTime + getVibratorOnDuration(waveform, currentIndex);
-        }
-    }
-
-    /** Represent a delay step with fixed duration, that starts counting when it starts playing. */
-    private final class DelayStep implements Step {
-        private final int mDelay;
-
-        DelayStep(int delay) {
-            mDelay = delay;
-        }
-
-        @Override
-        public Vibration.Status play() {
-            Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "DelayStep");
-            try {
-                if (DEBUG) {
-                    Slog.d(TAG, "DelayStep of " + mDelay + "ms starting...");
-                }
-                if (waitUntil(SystemClock.uptimeMillis() + mDelay)) {
-                    return Vibration.Status.FINISHED;
-                }
-                return Vibration.Status.CANCELLED;
-            } finally {
-                if (DEBUG) {
-                    Slog.d(TAG, "DelayStep done.");
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
-            }
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 426e631..9f60878 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1862,6 +1862,7 @@
         // Update application display metrics.
         final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(rotation);
         final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
+        final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
 
         final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
                 displayCutout);
@@ -1878,6 +1879,7 @@
                     CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
         }
         mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
+        mDisplayInfo.roundedCorners = roundedCorners;
         mDisplayInfo.getAppMetrics(mDisplayMetrics);
         if (mDisplayScalingDisabled) {
             mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
diff --git a/services/core/java/com/android/server/wm/DisplayHashController.java b/services/core/java/com/android/server/wm/DisplayHashController.java
index 7e16c22..1262dee 100644
--- a/services/core/java/com/android/server/wm/DisplayHashController.java
+++ b/services/core/java/com/android/server/wm/DisplayHashController.java
@@ -17,7 +17,9 @@
 package com.android.server.wm;
 
 import static android.service.displayhash.DisplayHasherService.EXTRA_VERIFIED_DISPLAY_HASH;
-import static android.service.displayhash.DisplayHasherService.SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM;
+import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
+import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -32,7 +34,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -45,16 +46,21 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.service.displayhash.DisplayHashParams;
 import android.service.displayhash.DisplayHasherService;
 import android.service.displayhash.IDisplayHasherService;
+import android.util.Size;
 import android.util.Slog;
 import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
 import android.view.displayhash.DisplayHash;
 import android.view.displayhash.VerifiedDisplayHash;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -78,12 +84,15 @@
     private final Context mContext;
 
     /**
-     * Lock used for the cached {@link #mHashAlgorithms} array
+     * Lock used for the cached {@link #mDisplayHashAlgorithms} map
      */
-    private final Object mHashAlgorithmsLock = new Object();
+    private final Object mDisplayHashAlgorithmsLock = new Object();
 
-    @GuardedBy("mHashingAlgorithmsLock")
-    private String[] mHashAlgorithms;
+    /**
+     * The cached map of display hash algorithms to the {@link DisplayHashParams}
+     */
+    @GuardedBy("mDisplayHashAlgorithmsLock")
+    private Map<String, DisplayHashParams> mDisplayHashAlgorithms;
 
     private final Handler mHandler;
 
@@ -104,34 +113,8 @@
     }
 
     String[] getSupportedHashAlgorithms() {
-        // We have a separate lock for the hashing algorithm array since it doesn't need to make
-        // the request through the service connection. Instead, we have a lock to ensure we can
-        // properly cache the hashing algorithms array so we don't need to call into the
-        // ExtServices process for each request.
-        synchronized (mHashAlgorithmsLock) {
-            // Already have cached values
-            if (mHashAlgorithms != null) {
-                return mHashAlgorithms;
-            }
-
-            final ServiceInfo serviceInfo = getServiceInfo();
-            if (serviceInfo == null) return null;
-
-            final PackageManager pm = mContext.getPackageManager();
-            final Resources res;
-            try {
-                res = pm.getResourcesForApplication(serviceInfo.applicationInfo);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.e(TAG, "Error getting application resources for " + serviceInfo, e);
-                return null;
-            }
-
-            final int resourceId = serviceInfo.metaData.getInt(
-                    SERVICE_META_DATA_KEY_AVAILABLE_ALGORITHMS);
-            mHashAlgorithms = res.getStringArray(resourceId);
-
-            return mHashAlgorithms;
-        }
+        Map<String, DisplayHashParams> displayHashAlgorithms = getDisplayHashAlgorithms();
+        return displayHashAlgorithms.keySet().toArray(new String[0]);
     }
 
     @Nullable
@@ -148,13 +131,76 @@
         return results.getParcelable(EXTRA_VERIFIED_DISPLAY_HASH);
     }
 
-    void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
+    private void generateDisplayHash(HardwareBuffer buffer, Rect bounds,
             String hashAlgorithm, RemoteCallback callback) {
         connectAndRun(
                 service -> service.generateDisplayHash(mSalt, buffer, bounds, hashAlgorithm,
                         callback));
     }
 
+    void generateDisplayHash(SurfaceControl.LayerCaptureArgs.Builder args,
+            Rect boundsInWindow, String hashAlgorithm, RemoteCallback callback) {
+        final Map<String, DisplayHashParams> displayHashAlgorithmsMap = getDisplayHashAlgorithms();
+        DisplayHashParams displayHashParams = displayHashAlgorithmsMap.get(hashAlgorithm);
+        if (displayHashParams == null) {
+            Slog.w(TAG, "Failed to generateDisplayHash. Invalid hashAlgorithm");
+            sendDisplayHashError(callback, DISPLAY_HASH_ERROR_INVALID_HASH_ALGORITHM);
+            return;
+        }
+
+        Size size = displayHashParams.getBufferSize();
+        if (size != null && (size.getWidth() > 0 || size.getHeight() > 0)) {
+            args.setFrameScale((float) size.getWidth() / boundsInWindow.width(),
+                    (float) size.getHeight() / boundsInWindow.height());
+        }
+
+        args.setGrayscale(displayHashParams.isGrayscaleBuffer());
+
+        SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
+                SurfaceControl.captureLayers(args.build());
+        if (screenshotHardwareBuffer == null
+                || screenshotHardwareBuffer.getHardwareBuffer() == null) {
+            Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
+            sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
+            return;
+        }
+
+        generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(), boundsInWindow,
+                hashAlgorithm, callback);
+    }
+
+    private Map<String, DisplayHashParams> getDisplayHashAlgorithms() {
+        // We have a separate lock for the hashing params to ensure we can properly cache the
+        // hashing params so we don't need to call into the ExtServices process for each request.
+        synchronized (mDisplayHashAlgorithmsLock) {
+            if (mDisplayHashAlgorithms != null) {
+                return mDisplayHashAlgorithms;
+            }
+
+            final SyncCommand syncCommand = new SyncCommand();
+            Bundle results = syncCommand.run((service, remoteCallback) -> {
+                try {
+                    service.getDisplayHashAlgorithms(remoteCallback);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to invoke getDisplayHashAlgorithms command", e);
+                }
+            });
+
+            mDisplayHashAlgorithms = new HashMap<>(results.size());
+            for (String key : results.keySet()) {
+                mDisplayHashAlgorithms.put(key, results.getParcelable(key));
+            }
+
+            return mDisplayHashAlgorithms;
+        }
+    }
+
+    void sendDisplayHashError(RemoteCallback callback, int errorCode) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
+        callback.sendResult(bundle);
+    }
+
     /**
      * Calculate the bounds to generate the hash for. This takes into account window transform,
      * magnification, and display bounds.
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 999c585..62e4a85 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -16,12 +16,15 @@
 
 package com.android.server.wm;
 
+import static java.lang.Integer.toHexString;
+
 import android.app.UriGrantsManager;
 import android.content.ClipData;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.internal.view.IDragAndDropPermissions;
 import com.android.server.LocalServices;
@@ -32,6 +35,9 @@
 class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
         implements IBinder.DeathRecipient {
 
+    private static final String TAG = "DragAndDrop";
+    private static final boolean DEBUG = false;
+
     private final WindowManagerGlobalLock mGlobalLock;
     private final int mSourceUid;
     private final String mTargetPackage;
@@ -43,7 +49,7 @@
 
     private IBinder mActivityToken = null;
     private IBinder mPermissionOwnerToken = null;
-    private IBinder mTransientToken = null;
+    private IBinder mAppToken = null;
 
     DragAndDropPermissionsHandler(WindowManagerGlobalLock lock, ClipData clipData, int sourceUid,
             String targetPackage, int mode, int sourceUserId, int targetUserId) {
@@ -62,6 +68,10 @@
         if (mActivityToken != null || mPermissionOwnerToken != null) {
             return;
         }
+        if (DEBUG) {
+            Log.d(TAG, this + ": taking permissions bound to activity: "
+                    + toHexString(activityToken.hashCode()));
+        }
         mActivityToken = activityToken;
 
         // Will throw if Activity is not found.
@@ -84,14 +94,18 @@
     }
 
     @Override
-    public void takeTransient(IBinder transientToken) throws RemoteException {
+    public void takeTransient(IBinder appToken) throws RemoteException {
         if (mActivityToken != null || mPermissionOwnerToken != null) {
             return;
         }
+        if (DEBUG) {
+            Log.d(TAG, this + ": taking permissions bound to app process: "
+                    + toHexString(appToken.hashCode()));
+        }
         mPermissionOwnerToken = LocalServices.getService(UriGrantsManagerInternal.class)
                 .newUriPermissionOwner("drop");
-        mTransientToken = transientToken;
-        mTransientToken.linkToDeath(this, 0);
+        mAppToken = appToken;
+        mAppToken.linkToDeath(this, 0);
 
         doTake(mPermissionOwnerToken);
     }
@@ -112,11 +126,17 @@
             } finally {
                 mActivityToken = null;
             }
+            if (DEBUG) {
+                Log.d(TAG, this + ": releasing activity-bound permissions");
+            }
         } else {
             permissionOwner = mPermissionOwnerToken;
             mPermissionOwnerToken = null;
-            mTransientToken.unlinkToDeath(this, 0);
-            mTransientToken = null;
+            mAppToken.unlinkToDeath(this, 0);
+            mAppToken = null;
+            if (DEBUG) {
+                Log.d(TAG, this + ": releasing process-bound permissions");
+            }
         }
 
         UriGrantsManagerInternal ugm = LocalServices.getService(UriGrantsManagerInternal.class);
@@ -139,6 +159,9 @@
 
     @Override
     public void binderDied() {
+        if (DEBUG) {
+            Log.d(TAG, this + ": app process died: " + toHexString(mAppToken.hashCode()));
+        }
         try {
             release();
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 45c4233..35e5491 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -30,7 +30,6 @@
 import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
 import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
 import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
-import static com.android.server.wm.InsetsSourceProviderProto.FINISH_SEAMLESS_ROTATE_FRAME_NUMBER;
 import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
 import static com.android.server.wm.InsetsSourceProviderProto.IME_OVERRIDDEN_FRAME;
 import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
@@ -59,6 +58,7 @@
 import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 
 import java.io.PrintWriter;
+import java.util.function.Consumer;
 
 /**
  * Controller for a specific inset source on the server. It's called provider as it provides the
@@ -84,6 +84,16 @@
     private final Rect mImeOverrideFrame = new Rect();
     private boolean mIsLeashReadyForDispatching;
 
+    private final Consumer<Transaction> mSetLeashPositionConsumer = t -> {
+        if (mControl != null) {
+            final SurfaceControl leash = mControl.getLeash();
+            if (leash != null) {
+                final Point position = mControl.getSurfacePosition();
+                t.setPosition(leash, position.x, position.y);
+            }
+        }
+    };
+
     /** The visibility override from the current controlling window. */
     private boolean mClientVisible;
 
@@ -149,7 +159,6 @@
             // TODO: Ideally, we should wait for the animation to finish so previous window can
             // animate-out as new one animates-in.
             mWin.cancelAnimation();
-            mWin.mPendingPositionChanged = null;
             mWin.mProvidedInsetsSources.remove(mSource.getType());
         }
         ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win);
@@ -248,31 +257,16 @@
         if (mControl != null) {
             final Point position = getWindowFrameSurfacePosition();
             if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
-                if (!mWin.getWindowFrames().didFrameSizeChange()) {
-                    updateLeashPosition(-1 /* frameNumber */);
-                } else if (mWin.mInRelayout) {
-                    updateLeashPosition(mWin.getFrameNumber());
+                if (mWin.getWindowFrames().didFrameSizeChange()) {
+                    mWin.applyWithNextDraw(mSetLeashPositionConsumer);
                 } else {
-                    mWin.mPendingPositionChanged = this;
+                    mSetLeashPositionConsumer.accept(mWin.getPendingTransaction());
                 }
                 mStateController.notifyControlChanged(mControlTarget);
             }
         }
     }
 
-    void updateLeashPosition(long frameNumber) {
-        if (mControl == null) {
-            return;
-        }
-        final SurfaceControl leash = mControl.getLeash();
-        if (leash != null) {
-            final Transaction t = mDisplayContent.getPendingTransaction();
-            final Point position = mControl.getSurfacePosition();
-            t.setPosition(leash, position.x, position.y);
-            deferTransactionUntil(t, leash, frameNumber);
-        }
-    }
-
     private Point getWindowFrameSurfacePosition() {
         final Rect frame = mWin.getFrame();
         final Point position = new Point();
@@ -280,14 +274,6 @@
         return position;
     }
 
-    private void deferTransactionUntil(Transaction t, SurfaceControl leash, long frameNumber) {
-        if (frameNumber >= 0) {
-            final SurfaceControl barrier = mWin.getClientViewRootSurface();
-            t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
-            t.deferTransactionUntil(leash, barrier, frameNumber);
-        }
-    }
-
     /**
      * @see InsetsStateController#onControlFakeTargetChanged(int, InsetsControlTarget)
      */
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 1212302..2a768e7 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2818,7 +2818,7 @@
      * @param launchParams   The resolved launch params to use.
      * @param realCallingPid The pid from {@link ActivityStarter#setRealCallingPid}
      * @param realCallingUid The uid from {@link ActivityStarter#setRealCallingUid}
-     * @return The roott task to use for the launch or INVALID_TASK_ID.
+     * @return The root task to use for the launch or INVALID_TASK_ID.
      */
     Task getLaunchRootTask(@Nullable ActivityRecord r,
             @Nullable ActivityOptions options, @Nullable Task candidateTask, boolean onTop,
@@ -2887,7 +2887,7 @@
                 // Falling back to default task container
                 taskDisplayArea = taskDisplayArea.mDisplayContent.getDefaultTaskDisplayArea();
                 rootTask = taskDisplayArea.getOrCreateRootTask(r, options, candidateTask,
-                        activityType, onTop);
+                        launchParams, activityType, onTop);
                 if (rootTask != null) {
                     return rootTask;
                 }
@@ -2942,7 +2942,8 @@
             }
         }
 
-        return container.getOrCreateRootTask(r, options, candidateTask, activityType, onTop);
+        return container.getOrCreateRootTask(
+                r, options, candidateTask, launchParams, activityType, onTop);
     }
 
     /** @return true if activity record is null or can be launched on provided display. */
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index 6c46135..0708569 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -52,8 +52,8 @@
             int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
             int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
         if (!DEBUG_ENABLE_SHELL_DRAWER) {
-            return mService.mPolicy.addSplashScreen(activity.token, packageName, theme,
-                    compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
+            return mService.mPolicy.addSplashScreen(activity.token, activity.mUserId, packageName,
+                    theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                     overrideConfig, displayId);
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 91aa48e..498fc5c 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -57,6 +57,7 @@
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.util.function.pooled.PooledPredicate;
+import com.android.server.wm.LaunchParamsController.LaunchParams;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -1072,11 +1073,17 @@
      * @see #getOrCreateRootTask(int, int, boolean)
      */
     Task getOrCreateRootTask(@Nullable ActivityRecord r,
-            @Nullable ActivityOptions options, @Nullable Task candidateTask, int activityType,
-            boolean onTop) {
-        // First preference is the windowing mode in the activity options if set.
-        int windowingMode = (options != null)
-                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+            @Nullable ActivityOptions options, @Nullable Task candidateTask,
+            @Nullable LaunchParams launchParams, int activityType, boolean onTop) {
+        int windowingMode = WINDOWING_MODE_UNDEFINED;
+        if (launchParams != null) {
+            // If launchParams isn't null, windowing mode is already resolved.
+            windowingMode = launchParams.mWindowingMode;
+        } else if (options != null) {
+            // If launchParams is null and options isn't let's use the windowing mode in the
+            // options.
+            windowingMode = options.getLaunchWindowingMode();
+        }
         // Validate that our desired windowingMode will work under the current conditions.
         // UNDEFINED windowing mode is a valid result and means that the new root task will inherit
         // it's display's windowing mode.
diff --git a/services/core/java/com/android/server/wm/WindowFrames.java b/services/core/java/com/android/server/wm/WindowFrames.java
index 9245f8c..ffd6d21 100644
--- a/services/core/java/com/android/server/wm/WindowFrames.java
+++ b/services/core/java/com/android/server/wm/WindowFrames.java
@@ -113,7 +113,7 @@
     }
 
     /**
-     * @return true if the width or height has changed since last reported to the client.
+     * @return true if the width or height has changed since last updating resizing window.
      */
     boolean didFrameSizeChange() {
         return (mLastFrame.width() != mFrame.width()) || (mLastFrame.height() != mFrame.height());
@@ -135,6 +135,13 @@
     }
 
     /**
+     * @return true if the width or height has changed since last reported to the client.
+     */
+    boolean isFrameSizeChangeReported() {
+        return mFrameSizeChanged || didFrameSizeChange();
+    }
+
+    /**
      * Resets the size changed flags so they're all set to false again. This should be called
      * after the frames are reported to client.
      */
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e70ad6f..f0dc7fe 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -84,8 +84,6 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
-import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_UNKNOWN;
-import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
@@ -2234,11 +2232,6 @@
 
             final DisplayContent dc = win.getDisplayContent();
 
-            if (win.mPendingPositionChanged != null) {
-                win.mPendingPositionChanged.updateLeashPosition(frameNumber);
-                win.mPendingPositionChanged = null;
-            }
-
             if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
                 win.prepareDrawHandlers();
                 result |= RELAYOUT_RES_BLAST_SYNC;
@@ -8638,14 +8631,16 @@
             final WindowState win = windowForClientLocked(session, window, false);
             if (win == null) {
                 Slog.w(TAG, "Failed to generate DisplayHash. Invalid window");
-                sendDisplayHashError(callback, DISPLAY_HASH_ERROR_MISSING_WINDOW);
+                mDisplayHashController.sendDisplayHashError(callback,
+                        DISPLAY_HASH_ERROR_MISSING_WINDOW);
                 return;
             }
 
             DisplayContent displayContent = win.getDisplayContent();
             if (displayContent == null) {
                 Slog.w(TAG, "Failed to generate DisplayHash. Window is not on a display");
-                sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+                mDisplayHashController.sendDisplayHashError(callback,
+                        DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
                 return;
             }
 
@@ -8655,7 +8650,8 @@
 
             if (boundsInDisplay.isEmpty()) {
                 Slog.w(TAG, "Failed to generate DisplayHash. Bounds are not on screen");
-                sendDisplayHashError(callback, DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
+                mDisplayHashController.sendDisplayHashError(callback,
+                        DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN);
                 return;
             }
         }
@@ -8665,23 +8661,13 @@
         // be covering it with the same uid. We want to make sure we include content that's
         // covering to ensure we get as close as possible to what the user sees
         final int uid = session.mUid;
-        SurfaceControl.LayerCaptureArgs args =
+        SurfaceControl.LayerCaptureArgs.Builder args =
                 new SurfaceControl.LayerCaptureArgs.Builder(displaySurfaceControl)
                         .setUid(uid)
-                        .setSourceCrop(boundsInDisplay)
-                        .build();
+                        .setSourceCrop(boundsInDisplay);
 
-        SurfaceControl.ScreenshotHardwareBuffer screenshotHardwareBuffer =
-                SurfaceControl.captureLayers(args);
-        if (screenshotHardwareBuffer == null
-                || screenshotHardwareBuffer.getHardwareBuffer() == null) {
-            Slog.w(TAG, "Failed to generate DisplayHash. Couldn't capture content");
-            sendDisplayHashError(callback, DISPLAY_HASH_ERROR_UNKNOWN);
-            return;
-        }
-
-        mDisplayHashController.generateDisplayHash(screenshotHardwareBuffer.getHardwareBuffer(),
-                boundsInWindow, hashAlgorithm, callback);
+        mDisplayHashController.generateDisplayHash(args, boundsInWindow,
+                hashAlgorithm, callback);
     }
 
     boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) {
@@ -8699,10 +8685,4 @@
             return snapshot != null && snapshot.hasImeSurface();
         }
     }
-
-    private void sendDisplayHashError(RemoteCallback callback, int errorCode) {
-        Bundle bundle = new Bundle();
-        bundle.putInt(EXTRA_DISPLAY_HASH_ERROR_CODE, errorCode);
-        callback.sendResult(bundle);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2eadcd5..7ebc1cc 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -726,8 +726,6 @@
      */
     private InsetsState mFrozenInsetsState;
 
-    @Nullable InsetsSourceProvider mPendingPositionChanged;
-
     private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
     private KeyInterceptionInfo mKeyInterceptionInfo;
 
@@ -774,6 +772,12 @@
         updateSurfacePosition(t);
     };
 
+    private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
+        if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+            t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+        }
+    };
+
     /**
      * @see #setSurfaceTranslationY(int)
      */
@@ -2129,6 +2133,8 @@
                 : getTask().getWindowConfiguration().hasMovementAnimations();
         if (mToken.okToAnimate()
                 && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+                && !mWindowFrames.didFrameSizeChange()
+                && !surfaceInsetsChanging()
                 && !isDragResizing()
                 && hasMovementAnimation
                 && !mWinAnimator.mLastHidden
@@ -5318,13 +5324,17 @@
         // prior to the rotation.
         if (!mSurfaceAnimator.hasLeash() && mPendingSeamlessRotate == null
                 && !mLastSurfacePosition.equals(mSurfacePosition)) {
-            t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
+            final boolean frameSizeChanged = mWindowFrames.isFrameSizeChangeReported();
+            final boolean surfaceInsetsChanged = surfaceInsetsChanging();
+            final boolean surfaceSizeChanged = frameSizeChanged || surfaceInsetsChanged;
             mLastSurfacePosition.set(mSurfacePosition.x, mSurfacePosition.y);
-            if (surfaceInsetsChanging() && mWinAnimator.hasSurface()) {
+            if (surfaceInsetsChanged) {
                 mLastSurfaceInsets.set(mAttrs.surfaceInsets);
-                t.deferTransactionUntil(mSurfaceControl,
-                        mWinAnimator.mSurfaceController.mSurfaceControl,
-                        getFrameNumber());
+            }
+            if (surfaceSizeChanged) {
+                applyWithNextDraw(mSetSurfacePositionConsumer);
+            } else {
+                mSetSurfacePositionConsumer.accept(t);
             }
         }
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7cbb1c6..ac2281a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -15479,10 +15479,8 @@
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller));
 
-        String currentMode = mInjector.settingsGlobalGetString(PRIVATE_DNS_MODE);
-        if (currentMode == null) {
-            currentMode = ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE_FALLBACK;
-        }
+        final String currentMode =
+                ConnectivityManager.getPrivateDnsMode(mContext.getContentResolver());
         switch (currentMode) {
             case ConnectivityManager.PRIVATE_DNS_MODE_OFF:
                 return PRIVATE_DNS_MODE_OFF;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5fbf1c4..4e23609 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -116,6 +116,7 @@
 import com.android.server.clipboard.ClipboardService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.compat.PlatformCompatNative;
+import com.android.server.connectivity.PacProxyService;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.coverage.CoverageService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -1315,6 +1316,7 @@
         ConsumerIrService consumerIr = null;
         MmsServiceBroker mmsService = null;
         HardwarePropertiesManagerService hardwarePropertiesService = null;
+        PacProxyService pacProxyService = null;
 
         boolean disableSystemTextClassifier = SystemProperties.getBoolean(
                 "config.disable_systemtextclassifier", false);
@@ -1874,6 +1876,15 @@
                 t.traceEnd();
             }
 
+            t.traceBegin("StartPacProxyService");
+            try {
+                pacProxyService = new PacProxyService(context);
+                ServiceManager.addService(Context.PAC_PROXY_SERVICE, pacProxyService);
+            } catch (Throwable e) {
+                reportWtf("starting PacProxyService", e);
+            }
+            t.traceEnd();
+
             t.traceBegin("StartConnectivityService");
             // This has to be called after NetworkManagementService, NetworkStatsService
             // and NetworkPolicyManager because ConnectivityService needs to take these
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
index 44b9f44..872b955 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java
@@ -1114,6 +1114,30 @@
                 argThat(closeTo(newEndSpec)));
     }
 
+    @Test
+    public void testSetScale_toMagnifying_shouldNotifyActivatedState() {
+        setScaleToMagnifying();
+
+        verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(true));
+    }
+
+    @Test
+    public void testReset_afterMagnifying_shouldNotifyDeactivatedState() {
+        setScaleToMagnifying();
+
+        mFullScreenMagnificationController.reset(DISPLAY_0, mAnimationCallback);
+        verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(false));
+    }
+
+    private void setScaleToMagnifying() {
+        register(DISPLAY_0);
+        float scale = 2.0f;
+        PointF pivotPoint = INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER;
+
+        mFullScreenMagnificationController.setScale(DISPLAY_0, scale, pivotPoint.x, pivotPoint.y,
+                false, SERVICE_ID_1);
+    }
+
     private void initMockWindowManager() {
         for (int i = 0; i < DISPLAY_COUNT; i++) {
             when(mMockWindowManager.setMagnificationCallbacks(eq(i), any())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 0c3640c..84c76b7 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.accessibility.magnification;
 
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
+import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+
 import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
 
 import static org.junit.Assert.assertEquals;
@@ -24,6 +27,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doAnswer;
@@ -283,6 +287,27 @@
     }
 
     @Test
+    public void onWindowMagnificationActivationState_windowActivated_logWindowDuration() {
+        mMagnificationController.onWindowMagnificationActivationState(true);
+
+        mMagnificationController.onWindowMagnificationActivationState(false);
+
+        verify(mMagnificationController).logMagnificationUsageState(
+                eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW), anyLong());
+    }
+
+    @Test
+    public void
+            onFullScreenMagnificationActivationState_fullScreenActivated_logFullScreenDuration() {
+        mMagnificationController.onFullScreenMagnificationActivationState(true);
+
+        mMagnificationController.onFullScreenMagnificationActivationState(false);
+
+        verify(mMagnificationController).logMagnificationUsageState(
+                eq(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN), anyLong());
+    }
+
+    @Test
     public void onTouchInteractionStart_fullScreenAndCapabilitiesAll_showMagnificationButton()
             throws RemoteException {
         setMagnificationEnabled(MODE_FULLSCREEN);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
index f26c86c..955217c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationManagerTest.java
@@ -392,6 +392,23 @@
         assertFalse(mWindowMagnificationManager.isWindowMagnifierEnabled(TEST_DISPLAY));
     }
 
+    @Test
+    public void onWindowMagnificationActivationState_magnifierEnabled_notifyActivatedState() {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+
+        verify(mMockCallback).onWindowMagnificationActivationState(eq(true));
+    }
+
+    @Test
+    public void onWindowMagnificationActivationState_magnifierDisabled_notifyDeactivatedState() {
+        mWindowMagnificationManager.setConnection(mMockConnection.getConnection());
+        mWindowMagnificationManager.enableWindowMagnification(TEST_DISPLAY, 3.0f, NaN, NaN);
+        mWindowMagnificationManager.disableWindowMagnification(TEST_DISPLAY, true);
+
+        verify(mMockCallback).onWindowMagnificationActivationState(eq(false));
+    }
+
     private MotionEvent generatePointersDownEvent(PointF[] pointersLocation) {
         final int len = pointersLocation.length;
 
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
new file mode 100644
index 0000000..7afcbf7
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/stats/PlatformLoggerTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2021 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.appsearch.stats;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.SparseIntArray;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.appsearch.external.localstorage.MockPackageManager;
+import com.android.server.appsearch.external.localstorage.stats.CallStats;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class PlatformLoggerTest {
+    private static final int TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS = 100;
+    private static final int TEST_DEFAULT_SAMPLING_RATIO = 10;
+    private static final String TEST_PACKAGE_NAME = "packageName";
+    private MockPackageManager mMockPackageManager = new MockPackageManager();
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+        mContext =
+                new ContextWrapper(context) {
+                    @Override
+                    public PackageManager getPackageManager() {
+                        return mMockPackageManager.getMockPackageManager();
+                    }
+                };
+    }
+
+    @Test
+    public void testcreateExtraStatsLocked_nullSamplingRatioMap_returnsDefaultSamplingRatio() {
+        PlatformLogger logger = new PlatformLogger(
+                ApplicationProvider.getApplicationContext(),
+                UserHandle.USER_NULL,
+                new PlatformLogger.Config(
+                        TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                        TEST_DEFAULT_SAMPLING_RATIO,
+                        /*samplingRatioMap=*/ null));
+
+        // Make sure default sampling ratio is used if samplingMap is not provided.
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_UNKNOWN).mSamplingRatio).isEqualTo(
+                TEST_DEFAULT_SAMPLING_RATIO);
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
+                TEST_DEFAULT_SAMPLING_RATIO);
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_QUERY).mSamplingRatio).isEqualTo(
+                TEST_DEFAULT_SAMPLING_RATIO);
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
+                TEST_DEFAULT_SAMPLING_RATIO);
+    }
+
+
+    @Test
+    public void testcreateExtraStatsLocked_with_samplingRatioMap_returnsConfiguredSamplingRatio() {
+        int putDocumentSamplingRatio = 1;
+        int querySamplingRatio = 2;
+        final SparseIntArray samplingRatios = new SparseIntArray();
+        samplingRatios.put(CallStats.CALL_TYPE_PUT_DOCUMENT, putDocumentSamplingRatio);
+        samplingRatios.put(CallStats.CALL_TYPE_QUERY, querySamplingRatio);
+        PlatformLogger logger = new PlatformLogger(
+                ApplicationProvider.getApplicationContext(),
+                UserHandle.USER_NULL,
+                new PlatformLogger.Config(
+                        TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                        TEST_DEFAULT_SAMPLING_RATIO,
+                        samplingRatios));
+
+         // The default sampling ratio should be used if no sampling ratio is
+         // provided for certain call type.
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_INITIALIZE).mSamplingRatio).isEqualTo(
+                TEST_DEFAULT_SAMPLING_RATIO);
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_FLUSH).mSamplingRatio).isEqualTo(
+                TEST_DEFAULT_SAMPLING_RATIO);
+
+        // The configured sampling ratio is used if sampling ratio is available
+        // for certain call type.
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_PUT_DOCUMENT).mSamplingRatio).isEqualTo(
+                putDocumentSamplingRatio);
+        assertThat(logger.createExtraStatsLocked(TEST_PACKAGE_NAME,
+                CallStats.CALL_TYPE_QUERY).mSamplingRatio).isEqualTo(
+                querySamplingRatio);
+    }
+
+    @Test
+    public void testShouldLogForTypeLocked_trueWhenSampleRatioIsOne() {
+        final int samplingRatio = 1;
+        final String testPackageName = "packageName";
+        PlatformLogger logger = new PlatformLogger(
+                ApplicationProvider.getApplicationContext(),
+                UserHandle.USER_NULL,
+                new PlatformLogger.Config(
+                        TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                        samplingRatio,
+                        /* samplingMap=*/ null));
+
+        // Sample should always be logged for the first time if sampling is disabled(value is one).
+        assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+        assertThat(logger.createExtraStatsLocked(testPackageName,
+                CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+    }
+
+    @Test
+    public void testShouldLogForTypeLocked_falseWhenSampleRatioIsNegative() {
+        final int samplingRatio = -1;
+        final String testPackageName = "packageName";
+        PlatformLogger logger = new PlatformLogger(
+                ApplicationProvider.getApplicationContext(),
+                UserHandle.USER_NULL,
+                new PlatformLogger.Config(
+                        TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                        samplingRatio,
+                        /* samplingMap=*/ null));
+
+        // Makes sure sample will be excluded due to sampling if sample ratio is negative.
+        assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+        // Skipped count should be 0 since it doesn't pass the sampling.
+        assertThat(logger.createExtraStatsLocked(testPackageName,
+                CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+    }
+
+    @Test
+    public void testShouldLogForTypeLocked_falseWhenWithinCoolOffInterval() {
+        // Next sample won't be excluded due to sampling.
+        final int samplingRatio = 1;
+        // Next sample would guaranteed to be too close.
+        final int minTimeIntervalBetweenSamplesMillis = Integer.MAX_VALUE;
+        final String testPackageName = "packageName";
+        PlatformLogger logger = new PlatformLogger(
+                ApplicationProvider.getApplicationContext(),
+                UserHandle.USER_NULL,
+                new PlatformLogger.Config(
+                        minTimeIntervalBetweenSamplesMillis,
+                        samplingRatio,
+                        /* samplingMap=*/ null));
+        logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+        // Makes sure sample will be excluded due to rate limiting if samples are too close.
+        assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isFalse();
+        assertThat(logger.createExtraStatsLocked(testPackageName,
+                CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(1);
+    }
+
+    @Test
+    public void testShouldLogForTypeLocked_trueWhenOutsideOfCoolOffInterval() {
+        // Next sample won't be excluded due to sampling.
+        final int samplingRatio = 1;
+        // Next sample would guaranteed to be included.
+        final int minTimeIntervalBetweenSamplesMillis = 0;
+        final String testPackageName = "packageName";
+        PlatformLogger logger = new PlatformLogger(
+                ApplicationProvider.getApplicationContext(),
+                UserHandle.USER_NULL,
+                new PlatformLogger.Config(
+                        minTimeIntervalBetweenSamplesMillis,
+                        samplingRatio,
+                        /* samplingMap=*/ null));
+        logger.setLastPushTimeMillisLocked(SystemClock.elapsedRealtime());
+
+        // Makes sure sample will be logged if it is not too close to previous sample.
+        assertThat(logger.shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)).isTrue();
+        assertThat(logger.createExtraStatsLocked(testPackageName,
+                CallStats.CALL_TYPE_PUT_DOCUMENT).mSkippedSampleCount).isEqualTo(0);
+    }
+
+    /** Makes sure the caching works while getting the UID for calling package. */
+    @Test
+    public void testGetPackageUidAsUser() throws Exception {
+        final String testPackageName = "packageName";
+        final int testUid = 1234;
+        PlatformLogger logger = new PlatformLogger(
+                mContext,
+                mContext.getUserId(),
+                new PlatformLogger.Config(
+                        TEST_MIN_TIME_INTERVAL_BETWEEN_SAMPLES_MILLIS,
+                        TEST_DEFAULT_SAMPLING_RATIO,
+                        /* samplingMap=*/ null));
+        mMockPackageManager.mockGetPackageUidAsUser(testPackageName, mContext.getUserId(), testUid);
+
+        //
+        // First time, no cache
+        //
+        PlatformLogger.ExtraStats extraStats = logger.createExtraStatsLocked(testPackageName,
+                CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+        verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
+                eq(testPackageName), /*userId=*/ anyInt());
+        assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+
+        //
+        // Second time, we have cache
+        //
+        extraStats = logger.createExtraStatsLocked(testPackageName,
+                CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+        // Count is still one since we will use the cache
+        verify(mMockPackageManager.getMockPackageManager(), times(1)).getPackageUidAsUser(
+                eq(testPackageName), /*userId=*/ anyInt());
+        assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+
+        //
+        // Remove the cache and try again
+        //
+        assertThat(logger.removeCachedUidForPackage(testPackageName)).isEqualTo(testUid);
+        extraStats = logger.createExtraStatsLocked(testPackageName,
+                CallStats.CALL_TYPE_PUT_DOCUMENT);
+
+        // count increased by 1 since cache is cleared
+        verify(mMockPackageManager.getMockPackageManager(), times(2)).getPackageUidAsUser(
+                eq(testPackageName), /*userId=*/ anyInt());
+        assertThat(extraStats.mPackageUid).isEqualTo(testUid);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54825ee..c1b6101 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -65,6 +65,7 @@
     @Mock HysteresisLevels mScreenBrightnessThresholds;
     @Mock Handler mNoOpHandler;
     @Mock DisplayDevice mDisplayDevice;
+    @Mock HighBrightnessModeController mHbmController;
 
     private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
     @Before
@@ -90,7 +91,8 @@
                 BRIGHTNESS_MAX_FLOAT, DOZE_SCALE_FACTOR, LIGHT_SENSOR_RATE,
                 INITIAL_LIGHT_SENSOR_RATE, BRIGHTENING_LIGHT_DEBOUNCE_CONFIG,
                 DARKENING_LIGHT_DEBOUNCE_CONFIG, RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG,
-                mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mLogicalDisplay, mContext
+                mAmbientBrightnessThresholds, mScreenBrightnessThresholds, mLogicalDisplay,
+                mContext, mHbmController
         );
         controller.setLoggingEnabled(true);
 
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 47f3bf9..b5336e3 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -45,7 +45,6 @@
 import android.os.test.TestLooper;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
-import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -667,7 +666,6 @@
     @Test
     public void initCecVersion_limitToMinimumSupportedVersion() {
         mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
-        Log.e("MARVIN", "set setting CEC");
         mHdmiControlService.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
@@ -679,9 +677,7 @@
 
     @Test
     public void initCecVersion_limitToAtLeast1_4() {
-        Log.e("MARVIN", "set HAL CEC to 0");
         mNativeWrapper.setCecVersion(0x0);
-        Log.e("MARVIN", "set setting CEC to 2");
         mHdmiControlService.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
@@ -694,7 +690,6 @@
     @Test
     public void initCecVersion_useHighestMatchingVersion() {
         mNativeWrapper.setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_2_0);
-        Log.e("MARVIN", "set setting CEC");
         mHdmiControlService.getHdmiCecConfig().setIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
index 1b7e1ca..7d5eec0 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -107,7 +107,8 @@
         waitForCompletion(thread);
 
         verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
-        verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED));
+        verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
+                eq(Vibration.Status.IGNORED_UNSUPPORTED));
     }
 
     @Test
@@ -121,7 +122,8 @@
         waitForCompletion(thread);
 
         verify(mControllerCallbacks, never()).onComplete(anyInt(), eq(vibrationId));
-        verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.IGNORED));
+        verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId),
+                eq(Vibration.Status.IGNORED_UNSUPPORTED));
     }
 
     @Test
@@ -206,8 +208,8 @@
         thread.cancel();
         waitForCompletion(thread);
 
-        verify(mIBatteryStatsMock, never()).noteVibratorOn(eq(UID), anyLong());
-        verify(mIBatteryStatsMock, never()).noteVibratorOff(eq(UID));
+        verify(mIBatteryStatsMock).noteVibratorOn(eq(UID), anyLong());
+        verify(mIBatteryStatsMock).noteVibratorOff(eq(UID));
         verify(mThreadCallbacks).onVibrationEnded(eq(vibrationId), eq(Vibration.Status.CANCELLED));
         assertFalse(thread.getVibrators().get(VIBRATOR_ID).isVibrating());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index e22cda6..eba5634 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -42,16 +42,20 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.app.ActivityOptions;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.wm.LaunchParamsController.LaunchParams;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -87,6 +91,48 @@
     }
 
     @Test
+    public void getOrCreateLaunchRootRespectsResolvedWindowingMode() {
+        final Task rootTask = createTaskStackOnDisplay(
+                WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        rootTask.mCreatedByOrganizer = true;
+        final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
+        taskDisplayArea.setLaunchRootTask(
+                rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD});
+
+        final Task candidateRootTask = createTaskStackOnDisplay(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
+        final LaunchParams launchParams = new LaunchParams();
+        launchParams.mWindowingMode = WINDOWING_MODE_FREEFORM;
+
+        final Task actualRootTask = taskDisplayArea.getOrCreateRootTask(
+                activity, null /* options */, candidateRootTask,
+                launchParams, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        assertSame(rootTask, actualRootTask.getRootTask());
+    }
+
+    @Test
+    public void getOrCreateLaunchRootUsesActivityOptionsWindowingMode() {
+        final Task rootTask = createTaskStackOnDisplay(
+                WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        rootTask.mCreatedByOrganizer = true;
+        final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
+        taskDisplayArea.setLaunchRootTask(
+                rootTask, new int[]{WINDOWING_MODE_FREEFORM}, new int[]{ACTIVITY_TYPE_STANDARD});
+
+        final Task candidateRootTask = createTaskStackOnDisplay(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
+        final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+        final Task actualRootTask = taskDisplayArea.getOrCreateRootTask(
+                activity, options, candidateRootTask,
+                null /* launchParams */, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        assertSame(rootTask, actualRootTask.getRootTask());
+    }
+
+    @Test
     public void testActivityWithZBoost_taskDisplayAreaDoesNotMoveUp() {
         final Task stack = createTaskStackOnDisplay(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 7822a85..ae8e2de 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -100,9 +100,9 @@
     }
 
     @Override
-    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
-            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
-            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
+    public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
+            int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
+            int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
         final com.android.server.wm.WindowState window;
         final ActivityRecord activity;
         final WindowManagerService wm = mWmSupplier.get();
diff --git a/telecomm/java/android/telecom/CallDiagnosticService.java b/telecomm/java/android/telecom/CallDiagnosticService.java
index 809f2bc..5fb6b33 100644
--- a/telecomm/java/android/telecom/CallDiagnosticService.java
+++ b/telecomm/java/android/telecom/CallDiagnosticService.java
@@ -27,6 +27,8 @@
 import android.os.HandlerExecutor;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.telephony.Annotation;
+import android.telephony.ims.ImsReasonInfo;
 import android.util.ArrayMap;
 
 import com.android.internal.telecom.ICallDiagnosticService;
@@ -105,6 +107,12 @@
                 throws RemoteException {
             handleBluetoothCallQualityReport(qualityReport);
         }
+
+        @Override
+        public void notifyCallDisconnected(@NonNull String callId,
+                @NonNull DisconnectCause disconnectCause) throws RemoteException {
+            handleCallDisconnected(callId, disconnectCause);
+        }
     }
 
     /**
@@ -329,6 +337,32 @@
     }
 
     /**
+     * Handles a request from the Telecom framework to get a disconnect message from the
+     * {@link CallDiagnosticService}.
+     * @param callId The ID of the call.
+     * @param disconnectCause The telecom disconnect cause.
+     */
+    private void handleCallDisconnected(@NonNull String callId,
+            @NonNull DisconnectCause disconnectCause) {
+        Log.i(this, "handleCallDisconnected: call=%s; cause=%s", callId, disconnectCause);
+        DiagnosticCall diagnosticCall = mDiagnosticCallByTelecomCallId.get(callId);
+        CharSequence message;
+        if (disconnectCause.getImsReasonInfo() != null) {
+            message = diagnosticCall.onCallDisconnected(disconnectCause.getImsReasonInfo());
+        } else {
+            message = diagnosticCall.onCallDisconnected(
+                    disconnectCause.getTelephonyDisconnectCause(),
+                    disconnectCause.getTelephonyPreciseDisconnectCause());
+        }
+        try {
+            mAdapter.overrideDisconnectMessage(callId, message);
+        } catch (RemoteException e) {
+            Log.w(this, "handleCallDisconnected: call=%s; cause=%s; %s",
+                    callId, disconnectCause, e);
+        }
+    }
+
+    /**
      * Handles an incoming bluetooth call quality report from Telecom.  Notifies via
      * {@link CallDiagnosticService#onBluetoothCallQualityReportReceived(
      * BluetoothCallQualityReport)}.
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 1472a4ac..ed7b79f 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,9 +16,13 @@
 
 package android.telecom;
 
+import android.annotation.Nullable;
 import android.media.ToneGenerator;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.telephony.Annotation;
+import android.telephony.PreciseDisconnectCause;
+import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 
 import java.util.Objects;
@@ -112,6 +116,9 @@
     private CharSequence mDisconnectDescription;
     private String mDisconnectReason;
     private int mToneToPlay;
+    private int mTelephonyDisconnectCause;
+    private int mTelephonyPreciseDisconnectCause;
+    private ImsReasonInfo mImsReasonInfo;
 
     /**
      * Creates a new DisconnectCause.
@@ -155,11 +162,36 @@
      */
     public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
             int toneToPlay) {
+        this(code, label, description, reason, toneToPlay,
+                android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
+                PreciseDisconnectCause.ERROR_UNSPECIFIED,
+                null /* imsReasonInfo */);
+    }
+
+    /**
+     * Creates a new DisconnectCause instance.
+     * @param code The code for the disconnect cause.
+     * @param label The localized label to show to the user to explain the disconnect.
+     * @param description The localized description to show to the user to explain the disconnect.
+     * @param reason The reason for the disconnect.
+     * @param toneToPlay The tone to play on disconnect, as defined in {@link ToneGenerator}.
+     * @param telephonyDisconnectCause The Telephony disconnect cause.
+     * @param telephonyPreciseDisconnectCause The Telephony precise disconnect cause.
+     * @param imsReasonInfo The relevant {@link ImsReasonInfo}, or {@code null} if not available.
+     * @hide
+     */
+    public DisconnectCause(int code, CharSequence label, CharSequence description, String reason,
+            int toneToPlay, @Annotation.DisconnectCauses int telephonyDisconnectCause,
+            @Annotation.PreciseDisconnectCauses int telephonyPreciseDisconnectCause,
+            @Nullable ImsReasonInfo imsReasonInfo) {
         mDisconnectCode = code;
         mDisconnectLabel = label;
         mDisconnectDescription = description;
         mDisconnectReason = reason;
         mToneToPlay = toneToPlay;
+        mTelephonyDisconnectCause = telephonyDisconnectCause;
+        mTelephonyPreciseDisconnectCause = telephonyPreciseDisconnectCause;
+        mImsReasonInfo = imsReasonInfo;
     }
 
     /**
@@ -209,6 +241,33 @@
     }
 
     /**
+     * Returns the telephony {@link android.telephony.DisconnectCause} for the call.
+     * @return The disconnect cause.
+     * @hide
+     */
+    public @Annotation.DisconnectCauses int getTelephonyDisconnectCause() {
+        return mTelephonyDisconnectCause;
+    }
+
+    /**
+     * Returns the telephony {@link android.telephony.PreciseDisconnectCause} for the call.
+     * @return The precise disconnect cause.
+     * @hide
+     */
+    public @Annotation.PreciseDisconnectCauses int getTelephonyPreciseDisconnectCause() {
+        return mTelephonyPreciseDisconnectCause;
+    }
+
+    /**
+     * Returns the telephony {@link ImsReasonInfo} associated with the call disconnection.
+     * @return The {@link ImsReasonInfo} or {@code null} if not known.
+     * @hide
+     */
+    public @Nullable ImsReasonInfo getImsReasonInfo() {
+        return mImsReasonInfo;
+    }
+
+    /**
      * Returns the tone to play when disconnected.
      *
      * @return the tone as defined in {@link ToneGenerator} to play when disconnected.
@@ -217,7 +276,8 @@
         return mToneToPlay;
     }
 
-    public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR = new Creator<DisconnectCause>() {
+    public static final @android.annotation.NonNull Creator<DisconnectCause> CREATOR
+            = new Creator<DisconnectCause>() {
         @Override
         public DisconnectCause createFromParcel(Parcel source) {
             int code = source.readInt();
@@ -225,7 +285,11 @@
             CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
             String reason = source.readString();
             int tone = source.readInt();
-            return new DisconnectCause(code, label, description, reason, tone);
+            int telephonyDisconnectCause = source.readInt();
+            int telephonyPreciseDisconnectCause = source.readInt();
+            ImsReasonInfo imsReasonInfo = source.readParcelable(null);
+            return new DisconnectCause(code, label, description, reason, tone,
+                    telephonyDisconnectCause, telephonyPreciseDisconnectCause, imsReasonInfo);
         }
 
         @Override
@@ -241,6 +305,9 @@
         TextUtils.writeToParcel(mDisconnectDescription, destination, flags);
         destination.writeString(mDisconnectReason);
         destination.writeInt(mToneToPlay);
+        destination.writeInt(mTelephonyDisconnectCause);
+        destination.writeInt(mTelephonyPreciseDisconnectCause);
+        destination.writeParcelable(mImsReasonInfo, 0);
     }
 
     @Override
@@ -254,7 +321,10 @@
                 + Objects.hashCode(mDisconnectLabel)
                 + Objects.hashCode(mDisconnectDescription)
                 + Objects.hashCode(mDisconnectReason)
-                + Objects.hashCode(mToneToPlay);
+                + Objects.hashCode(mToneToPlay)
+                + Objects.hashCode(mTelephonyDisconnectCause)
+                + Objects.hashCode(mTelephonyPreciseDisconnectCause)
+                + Objects.hashCode(mImsReasonInfo);
     }
 
     @Override
@@ -265,7 +335,11 @@
                     && Objects.equals(mDisconnectLabel, d.getLabel())
                     && Objects.equals(mDisconnectDescription, d.getDescription())
                     && Objects.equals(mDisconnectReason, d.getReason())
-                    && Objects.equals(mToneToPlay, d.getTone());
+                    && Objects.equals(mToneToPlay, d.getTone())
+                    && Objects.equals(mTelephonyDisconnectCause, d.getTelephonyDisconnectCause())
+                    && Objects.equals(mTelephonyPreciseDisconnectCause,
+                    d.getTelephonyPreciseDisconnectCause())
+                    && Objects.equals(mImsReasonInfo, d.getImsReasonInfo());
         }
         return false;
     }
@@ -325,6 +399,11 @@
                 + " Label: (" + label + ")"
                 + " Description: (" + description + ")"
                 + " Reason: (" + reason + ")"
-                + " Tone: (" + mToneToPlay + ") ]";
+                + " Tone: (" + mToneToPlay + ") "
+                + " TelephonyCause: " + mTelephonyDisconnectCause + "/"
+                + mTelephonyPreciseDisconnectCause
+                + " ImsReasonInfo: "
+                + mImsReasonInfo
+                + "]";
     }
 }
diff --git a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
index 65b4d19..fc9879a 100644
--- a/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallDiagnosticService.aidl
@@ -18,6 +18,7 @@
 
 import android.telecom.BluetoothCallQualityReport;
 import android.telecom.CallAudioState;
+import android.telecom.DisconnectCause;
 import android.telecom.ParcelableCall;
 import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
 
@@ -34,4 +35,5 @@
     void removeDiagnosticCall(in String callId);
     void receiveDeviceToDeviceMessage(in String callId, int message, int value);
     void receiveBluetoothCallQualityReport(in BluetoothCallQualityReport qualityReport);
+    void notifyCallDisconnected(in String callId, in DisconnectCause disconnectCause);
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4dc6c7c..3084b14 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -14634,6 +14634,10 @@
     /**
      * Enable/Disable E-UTRA-NR Dual Connectivity.
      *
+     * This api is supported only if
+     * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+     * returns true.
      * @param nrDualConnectivityState expected NR dual connectivity state
      * This can be passed following states
      * <ol>
@@ -14648,6 +14652,9 @@
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(
+            enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+            value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
     public @EnableNrDualConnectivityResult int setNrDualConnectivityState(
             @NrDualConnectivityState int nrDualConnectivityState) {
         try {
@@ -14667,15 +14674,21 @@
 
     /**
      * Is E-UTRA-NR Dual Connectivity enabled.
+     * This api is supported only if
+     * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}
+     * ({@link TelephonyManager#CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE})
+     * returns true.
      * @return true if dual connectivity is enabled else false. Enabled state does not mean dual
      * connectivity is active. It means the device is allowed to connect to both primary and
      * secondary cell.
-     * <p>Requires Permission:
-     * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @hide
      */
     @SystemApi
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(
+            enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
+            value = TelephonyManager.CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE)
     public boolean isNrDualConnectivityEnabled() {
         try {
             ITelephony telephony = getITelephony();
@@ -14927,11 +14940,23 @@
     public static final String CAPABILITY_ALLOWED_NETWORK_TYPES_USED =
             "CAPABILITY_ALLOWED_NETWORK_TYPES_USED";
 
+    /**
+     * Indicates whether {@link #setNrDualConnectivityState()} and
+     * {@link #isNrDualConnectivityEnabled()} ()} are available.  See comments
+     * on respective methods for more information.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE =
+            "CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE";
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @StringDef(prefix = "CAPABILITY_", value = {
             CAPABILITY_SECONDARY_LINK_BANDWIDTH_VISIBLE,
             CAPABILITY_ALLOWED_NETWORK_TYPES_USED,
+            CAPABILITY_NR_DUAL_CONNECTIVITY_CONFIGURATION_AVAILABLE
     })
     public @interface RadioInterfaceCapability {}
 
diff --git a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
index e121b68..df58da5 100644
--- a/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
+++ b/tests/PlatformCompatGating/test-rules/src/android/compat/testing/PlatformCompatChangeRule.java
@@ -100,7 +100,6 @@
                 platformCompat.setOverridesForTest(new CompatibilityChangeConfig(mConfig),
                         packageName);
                 try {
-                    uiAutomation.dropShellPermissionIdentity();
                     mTestStatement.evaluate();
                 } finally {
                     adoptShellPermissions(uiAutomation);
diff --git a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
index ce226fd..926ff4d 100644
--- a/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
+++ b/tests/SurfaceViewBufferTests/cpp/SurfaceProxy.cpp
@@ -130,6 +130,9 @@
         return result;
     }
     sBuffers[slot] = anb;
+    if (timeoutMs == 0) {
+        return android::OK;
+    }
     android::sp<android::Fence> fence(new android::Fence(fenceFd));
     int waitResult = fence->wait(timeoutMs);
     if (waitResult != android::OK) {
@@ -197,6 +200,28 @@
     return result;
 }
 
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetAsyncMode(JNIEnv* /* env */,
+                                                                              jclass /* clazz */,
+                                                                              jboolean async) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    return surface->setAsyncMode(async);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetDequeueTimeout(
+        JNIEnv* /* env */, jclass /* clazz */, jlong timeoutMs) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    return surface->setDequeueTimeout(timeoutMs);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_SurfaceSetMaxDequeuedBufferCount(
+        JNIEnv* /* env */, jclass /* clazz */, jint maxDequeuedBuffers) {
+    assert(sAnw);
+    android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
+    return surface->setMaxDequeuedBufferCount(maxDequeuedBuffers);
+}
+
 JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_NativeWindowSetBufferCount(
         JNIEnv* /* env */, jclass /* clazz */, jint count) {
     assert(sAnw);
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
index 7d278dc..b67dc380 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/BufferPresentationTests.kt
@@ -17,6 +17,7 @@
 
 import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
 import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -93,4 +94,80 @@
 
         assertThat(trace).hasFrameSequence("SurfaceView", 1..numFrames)
     }
+
+    @Test
+    // Leave IGBP in sync mode, try to dequeue and queue as fast as possible. Check that we
+    // occasionally get timeout errors.
+    fun testSyncMode_dequeueWithoutBlockingFails() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            var failures = false
+            for (i in 1..numFrames) {
+                if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+                    failures = true
+                    break
+                }
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+            assertTrue(failures)
+        }
+    }
+
+    @Test
+    // Set IGBP to be in async mode, try to dequeue and queue as fast as possible. Client should be
+    // able to dequeue and queue buffers without being blocked.
+    fun testAsyncMode_dequeueWithoutBlocking() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+            for (i in 1..numFrames) {
+                assertEquals(0, activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */))
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+        }
+    }
+
+    @Test
+    // Disable triple buffering in the system and leave IGBP in sync mode. Check that we
+    // occasionally get timeout errors.
+    fun testSyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            var failures = false
+            for (i in 1..numFrames) {
+                if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+                    failures = true
+                    break
+                }
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+            assertTrue(failures)
+        }
+    }
+
+    @Test
+    // Disable triple buffering in the system and set IGBP to be in async mode. Try to dequeue and
+    // queue as fast as possible. Without triple buffering, the client does not have an extra buffer
+    // to dequeue and will not be able to dequeue and queue buffers without being blocked.
+    fun testAsyncModeWithDisabledTripleBuffering_dequeueWithoutBlockingFails() {
+        val numFrames = 1000L
+        runOnUiThread { activity ->
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetMaxDequeuedBufferCount(1))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetDequeueTimeout(3L))
+            assertEquals(0, activity.mSurfaceProxy.SurfaceSetAsyncMode(async = true))
+            var failures = false
+            for (i in 1..numFrames) {
+                if (activity.mSurfaceProxy.SurfaceDequeueBuffer(0, 0 /* ms */) != 0) {
+                    failures = true
+                    break
+                }
+                activity.mSurfaceProxy.SurfaceQueueBuffer(0)
+            }
+            assertTrue(failures)
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
index cfbd3ac..45a7094 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceProxy.kt
@@ -54,7 +54,13 @@
     external fun SurfaceSetScalingMode(scalingMode: Int)
     external fun SurfaceDequeueBuffer(slot: Int, timeoutMs: Int): Int
     external fun SurfaceCancelBuffer(slot: Int)
-    external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true)
+    external fun SurfaceQueueBuffer(slot: Int, freeSlot: Boolean = true): Int
+    external fun SurfaceSetAsyncMode(async: Boolean): Int
+    external fun SurfaceSetDequeueTimeout(timeout: Long): Int
+    external fun SurfaceQuery(what: Int): Int
+    external fun SurfaceSetMaxDequeuedBufferCount(maxDequeuedBuffers: Int): Int
+
+    // system/native_window.h functions
     external fun NativeWindowSetBufferCount(count: Int): Int
     external fun NativeWindowSetSharedBufferMode(shared: Boolean): Int
     external fun NativeWindowSetAutoRefresh(autoRefresh: Boolean): Int
diff --git a/tests/net/common/java/android/net/IpPrefixTest.java b/tests/net/common/java/android/net/IpPrefixTest.java
index 9c0fc7c..50ecb42 100644
--- a/tests/net/common/java/android/net/IpPrefixTest.java
+++ b/tests/net/common/java/android/net/IpPrefixTest.java
@@ -113,6 +113,15 @@
             p = new IpPrefix("f00:::/32");
             fail("Expected IllegalArgumentException: invalid IPv6 address");
         } catch (IllegalArgumentException expected) { }
+
+        p = new IpPrefix("/64");
+        assertEquals("::/64", p.toString());
+
+        p = new IpPrefix("/128");
+        assertEquals("::1/128", p.toString());
+
+        p = new IpPrefix("[2001:db8::123]/64");
+        assertEquals("2001:db8::/64", p.toString());
     }
 
     @Test
diff --git a/tests/net/common/java/android/net/LinkAddressTest.java b/tests/net/common/java/android/net/LinkAddressTest.java
index 1eaf30c..2cf3cf9 100644
--- a/tests/net/common/java/android/net/LinkAddressTest.java
+++ b/tests/net/common/java/android/net/LinkAddressTest.java
@@ -53,6 +53,7 @@
 import org.junit.runner.RunWith;
 
 import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.net.InterfaceAddress;
 import java.net.NetworkInterface;
@@ -117,6 +118,20 @@
         assertEquals(456, address.getScope());
         assertTrue(address.isIpv4());
 
+        address = new LinkAddress("/64", 1 /* flags */, 2 /* scope */);
+        assertEquals(Inet6Address.LOOPBACK, address.getAddress());
+        assertEquals(64, address.getPrefixLength());
+        assertEquals(1, address.getFlags());
+        assertEquals(2, address.getScope());
+        assertTrue(address.isIpv6());
+
+        address = new LinkAddress("[2001:db8::123]/64", 3 /* flags */, 4 /* scope */);
+        assertEquals(InetAddresses.parseNumericAddress("2001:db8::123"), address.getAddress());
+        assertEquals(64, address.getPrefixLength());
+        assertEquals(3, address.getFlags());
+        assertEquals(4, address.getScope());
+        assertTrue(address.isIpv6());
+
         // InterfaceAddress doesn't have a constructor. Fetch some from an interface.
         List<InterfaceAddress> addrs = NetworkInterface.getByName("lo").getInterfaceAddresses();
 
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index a07bce3..8932892 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -30,12 +30,21 @@
  *     -e selectTest_verbose true \
  *     com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  * </pre>
+ *
+ * <p>Use this filter when running FrameworksMockingCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ *     -e filter com.android.server.wm.test.filters.FrameworksTestsFilter \
+ *     -e selectTest_verbose true \
+ *     com.android.frameworks.mockingcoretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
  */
 public final class FrameworksTestsFilter extends SelectTest {
 
     private static final String[] SELECTED_TESTS = {
             // Test specifications for FrameworksMockingCoreTests.
             "android.app.activity.ActivityThreadClientTest",
+            "android.view.DisplayTest",
             // Test specifications for FrameworksCoreTests.
             "android.app.servertransaction.", // all tests under the package.
             "android.view.CutoutSpecificationTest",