Merge "Allow embedding activities cross apps" into sc-v2-dev
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 941a1fa..44c55c2 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -410,7 +410,7 @@
     ],
     static_libs: [
         "android-non-updatable.stubs.module_lib",
-        "art.module.public.api.stubs",
+        "art.module.public.api.stubs.module_lib",
     ],
     dist: {
         dir: "apistubs/android/module-lib",
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
index 1be68f5..44e6c70 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.graphics.Point;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 import android.perftests.utils.PerfTestActivity;
@@ -124,6 +125,12 @@
     }
 
     private static class RelayoutRunner {
+        /**
+         * There may be some messages post to other threads with holding WM lock after relayout.
+         * Let it take a break to avoid lock contention that isn't the scope of this test.
+         */
+        private static final long INTERVAL_MS = 10;
+
         final ClientWindowFrames mOutFrames = new ClientWindowFrames();
         final MergedConfiguration mOutMergedConfiguration = new MergedConfiguration();
         final InsetsState mOutInsetsState = new InsetsState();
@@ -158,6 +165,9 @@
                         mViewVisibility.getAsInt(), mFlags, mFrameNumber, mOutFrames,
                         mOutMergedConfiguration, mOutSurfaceControl, mOutInsetsState, mOutControls,
                         mOutSurfaceSize);
+                state.pauseTiming();
+                SystemClock.sleep(INTERVAL_MS);
+                state.resumeTiming();
             }
         }
     }
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index a2dc1c2..452bb0a 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -29,6 +29,7 @@
 import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
@@ -82,7 +83,7 @@
 
     private static class TestWindow extends BaseIWindow {
         final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
-        final InsetsState mRequestedVisibility = new InsetsState();
+        final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
         final InsetsState mOutInsetsState = new InsetsState();
         final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
 
@@ -102,7 +103,7 @@
 
                 long startTime = SystemClock.elapsedRealtimeNanos();
                 session.addToDisplay(this, mLayoutParams, View.VISIBLE,
-                        Display.DEFAULT_DISPLAY, mRequestedVisibility, inputChannel,
+                        Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel,
                         mOutInsetsState, mOutControls);
                 final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
                 state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
index c44fd40..29048b2 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
@@ -81,6 +81,14 @@
             "sampling_interval_for_batch_call_stats";
     public static final String KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS =
             "sampling_interval_for_put_document_stats";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS =
+            "sampling_interval_for_initialize_stats";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS =
+            "sampling_interval_for_search_stats";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS =
+            "sampling_interval_for_global_search_stats";
+    public static final String KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS =
+            "sampling_interval_for_optimize_stats";
     public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES =
             "limit_config_max_document_size_bytes";
     public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT =
@@ -95,6 +103,10 @@
             KEY_SAMPLING_INTERVAL_DEFAULT,
             KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
             KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
+            KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
+            KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
+            KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
+            KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
             KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
             KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
             KEY_BYTES_OPTIMIZE_THRESHOLD,
@@ -245,6 +257,58 @@
         }
     }
 
+    /**
+     * Returns cached value for sampling interval for initialize.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForInitializeStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
+    /**
+     * Returns cached value for sampling interval for search.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForSearchStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
+    /**
+     * Returns cached value for sampling interval for globalSearch.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForGlobalSearchStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
+    /**
+     * Returns cached value for sampling interval for optimize.
+     *
+     * <p>For example, sampling_interval=10 means that one out of every 10 stats was logged.
+     */
+    public int getCachedSamplingIntervalForOptimizeStats() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
+                    getCachedSamplingIntervalDefault());
+        }
+    }
+
     /** Returns the maximum serialized size an indexed document can be, in bytes. */
     public int getCachedLimitConfigMaxDocumentSizeBytes() {
         synchronized (mLock) {
@@ -343,6 +407,10 @@
             case KEY_SAMPLING_INTERVAL_DEFAULT:
             case KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS:
             case KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS:
+            case KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS:
+            case KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS:
+            case KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS:
+            case KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS:
                 synchronized (mLock) {
                     mBundleLocked.putInt(key, properties.getInt(key, DEFAULT_SAMPLING_INTERVAL));
                 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 1d66beb..db23a6d 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -59,6 +59,7 @@
 import com.android.server.LocalManagerRegistry;
 import com.android.server.SystemService;
 import com.android.server.appsearch.external.localstorage.stats.CallStats;
+import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
 import com.android.server.appsearch.stats.StatsCollector;
 import com.android.server.appsearch.util.PackageUtil;
@@ -1479,20 +1480,42 @@
 
     private void checkForOptimize(AppSearchUserInstance instance, int mutateBatchSize) {
         EXECUTOR.execute(() -> {
+            long totalLatencyStartMillis = SystemClock.elapsedRealtime();
+            OptimizeStats.Builder builder = new OptimizeStats.Builder();
             try {
-                instance.getAppSearchImpl().checkForOptimize(mutateBatchSize);
+                instance.getAppSearchImpl().checkForOptimize(mutateBatchSize, builder);
             } catch (AppSearchException e) {
                 Log.w(TAG, "Error occurred when check for optimize", e);
+            } finally {
+                OptimizeStats oStats = builder
+                        .setTotalLatencyMillis(
+                                (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis))
+                        .build();
+                if (oStats.getOriginalDocumentCount() > 0) {
+                    // see if optimize has been run by checking originalDocumentCount
+                    instance.getLogger().logStats(oStats);
+                }
             }
         });
     }
 
     private void checkForOptimize(AppSearchUserInstance instance) {
         EXECUTOR.execute(() -> {
+            long totalLatencyStartMillis = SystemClock.elapsedRealtime();
+            OptimizeStats.Builder builder = new OptimizeStats.Builder();
             try {
-                instance.getAppSearchImpl().checkForOptimize();
+                instance.getAppSearchImpl().checkForOptimize(builder);
             } catch (AppSearchException e) {
                 Log.w(TAG, "Error occurred when check for optimize", e);
+            } finally {
+                OptimizeStats oStats = builder
+                        .setTotalLatencyMillis(
+                                (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis))
+                        .build();
+                if (oStats.getOriginalDocumentCount() > 0) {
+                    // see if optimize has been run by checking originalDocumentCount
+                    instance.getLogger().logStats(oStats);
+                }
             }
         });
     }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 830e76c..15916cc 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -55,6 +55,7 @@
 import com.android.server.appsearch.external.localstorage.converter.SetSchemaResponseToProtoConverter;
 import com.android.server.appsearch.external.localstorage.converter.TypePropertyPathToProtoConverter;
 import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
+import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
 import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
 import com.android.server.appsearch.external.localstorage.stats.SearchStats;
@@ -145,6 +146,9 @@
 public final class AppSearchImpl implements Closeable {
     private static final String TAG = "AppSearchImpl";
 
+    /** A value 0 means that there're no more pages in the search results. */
+    private static final long EMPTY_PAGE_TOKEN = 0;
+
     @VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
 
     private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
@@ -284,6 +288,9 @@
 
                 // Log the time it took to read the data that goes into the cache maps
                 if (initStatsBuilder != null) {
+                    // In case there is some error for getAllNamespaces, we can still
+                    // set the latency for preparation.
+                    // If there is no error, the value will be overridden by the actual one later.
                     initStatsBuilder
                             .setStatusCode(
                                     statusProtoToResultCode(
@@ -1135,6 +1142,16 @@
                     searchResultProto.getResultsCount(),
                     searchResultProto);
             checkSuccess(searchResultProto.getStatus());
+            if (nextPageToken != EMPTY_PAGE_TOKEN
+                    && searchResultProto.getNextPageToken() == EMPTY_PAGE_TOKEN) {
+                // At this point, we're guaranteed that this nextPageToken exists for this package,
+                // otherwise checkNextPageToken would've thrown an exception.
+                // Since the new token is 0, this is the last page. We should remove the old token
+                // from our cache since it no longer refers to this query.
+                synchronized (mNextPageTokensLocked) {
+                    mNextPageTokensLocked.get(packageName).remove(nextPageToken);
+                }
+            }
             return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
         } finally {
             mReadWriteLock.readLock().unlock();
@@ -1326,8 +1343,7 @@
                     deleteResultProto.getStatus(), StatusProto.Code.OK, StatusProto.Code.NOT_FOUND);
 
             // Update derived maps
-            int numDocumentsDeleted =
-                    deleteResultProto.getDeleteStats().getNumDocumentsDeleted();
+            int numDocumentsDeleted = deleteResultProto.getDeleteStats().getNumDocumentsDeleted();
             updateDocumentCountAfterRemovalLocked(packageName, numDocumentsDeleted);
         } finally {
             mReadWriteLock.writeLock().unlock();
@@ -2056,6 +2072,10 @@
     }
 
     private void addNextPageToken(String packageName, long nextPageToken) {
+        if (nextPageToken == EMPTY_PAGE_TOKEN) {
+            // There is no more pages. No need to add it.
+            return;
+        }
         synchronized (mNextPageTokensLocked) {
             Set<Long> tokens = mNextPageTokensLocked.get(packageName);
             if (tokens == null) {
@@ -2068,6 +2088,11 @@
 
     private void checkNextPageToken(String packageName, long nextPageToken)
             throws AppSearchException {
+        if (nextPageToken == EMPTY_PAGE_TOKEN) {
+            // Swallow the check for empty page token, token = 0 means there is no more page and it
+            // won't return anything from Icing.
+            return;
+        }
         synchronized (mNextPageTokensLocked) {
             Set<Long> nextPageTokens = mNextPageTokensLocked.get(packageName);
             if (nextPageTokens == null || !nextPageTokens.contains(nextPageToken)) {
@@ -2162,12 +2187,13 @@
      *     #CHECK_OPTIMIZE_INTERVAL}, {@link IcingSearchEngine#getOptimizeInfo()} will be triggered
      *     and the counter will be reset.
      */
-    public void checkForOptimize(int mutationSize) throws AppSearchException {
+    public void checkForOptimize(int mutationSize, @Nullable OptimizeStats.Builder builder)
+            throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
             mOptimizeIntervalCountLocked += mutationSize;
             if (mOptimizeIntervalCountLocked >= CHECK_OPTIMIZE_INTERVAL) {
-                checkForOptimize();
+                checkForOptimize(builder);
             }
         } finally {
             mReadWriteLock.writeLock().unlock();
@@ -2183,14 +2209,15 @@
      * <p>{@link IcingSearchEngine#optimize()} should be called only if {@link
      * OptimizeStrategy#shouldOptimize(GetOptimizeInfoResultProto)} return true.
      */
-    public void checkForOptimize() throws AppSearchException {
+    public void checkForOptimize(@Nullable OptimizeStats.Builder builder)
+            throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
             GetOptimizeInfoResultProto optimizeInfo = getOptimizeInfoResultLocked();
             checkSuccess(optimizeInfo.getStatus());
             mOptimizeIntervalCountLocked = 0;
             if (mOptimizeStrategy.shouldOptimize(optimizeInfo)) {
-                optimize();
+                optimize(builder);
             }
         } finally {
             mReadWriteLock.writeLock().unlock();
@@ -2201,13 +2228,18 @@
     }
 
     /** Triggers {@link IcingSearchEngine#optimize()} directly. */
-    public void optimize() throws AppSearchException {
+    public void optimize(@Nullable OptimizeStats.Builder builder) throws AppSearchException {
         mReadWriteLock.writeLock().lock();
         try {
             mLogUtil.piiTrace("optimize, request");
             OptimizeResultProto optimizeResultProto = mIcingSearchEngineLocked.optimize();
             mLogUtil.piiTrace(
                     "optimize, response", optimizeResultProto.getStatus(), optimizeResultProto);
+            if (builder != null) {
+                builder.setStatusCode(statusProtoToResultCode(optimizeResultProto.getStatus()));
+                AppSearchLoggerHelper.copyNativeStats(
+                        optimizeResultProto.getOptimizeStats(), builder);
+            }
             checkSuccess(optimizeResultProto.getStatus());
         } finally {
             mReadWriteLock.writeLock().unlock();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java
index d92f4f0..98cedc7 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLogger.java
@@ -17,10 +17,10 @@
 package com.android.server.appsearch.external.localstorage;
 
 import android.annotation.NonNull;
-import android.app.appsearch.exceptions.AppSearchException;
 
 import com.android.server.appsearch.external.localstorage.stats.CallStats;
 import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
+import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
 import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
 import com.android.server.appsearch.external.localstorage.stats.SearchStats;
@@ -37,19 +37,22 @@
  */
 public interface AppSearchLogger {
     /** Logs {@link CallStats} */
-    void logStats(@NonNull CallStats stats) throws AppSearchException;
+    void logStats(@NonNull CallStats stats);
 
     /** Logs {@link PutDocumentStats} */
-    void logStats(@NonNull PutDocumentStats stats) throws AppSearchException;
+    void logStats(@NonNull PutDocumentStats stats);
 
     /** Logs {@link InitializeStats} */
-    void logStats(@NonNull InitializeStats stats) throws AppSearchException;
+    void logStats(@NonNull InitializeStats stats);
 
     /** Logs {@link SearchStats} */
-    void logStats(@NonNull SearchStats stats) throws AppSearchException;
+    void logStats(@NonNull SearchStats stats);
 
     /** Logs {@link RemoveStats} */
-    void logStats(@NonNull RemoveStats stats) throws AppSearchException;
+    void logStats(@NonNull RemoveStats stats);
+
+    /** Logs {@link OptimizeStats} */
+    void logStats(@NonNull OptimizeStats stats);
 
     // TODO(b/173532925) Add remaining logStats once we add all the stats.
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
index aa9200a..cd653e5 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
@@ -19,12 +19,14 @@
 import android.annotation.NonNull;
 
 import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
+import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
 import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
 import com.android.server.appsearch.external.localstorage.stats.SearchStats;
 
 import com.google.android.icing.proto.DeleteStatsProto;
 import com.google.android.icing.proto.InitializeStatsProto;
+import com.google.android.icing.proto.OptimizeStatsProto;
 import com.google.android.icing.proto.PutDocumentStatsProto;
 import com.google.android.icing.proto.QueryStatsProto;
 
@@ -92,8 +94,8 @@
                 .setSchemaTypeCount(fromNativeStats.getNumSchemaTypes());
     }
 
-    /*
-     * Copy native Query stats to buiilder.
+    /**
+     * Copies native Query stats to builder.
      *
      * @param fromNativeStats Stats copied from.
      * @param toStatsBuilder Stats copied to.
@@ -122,8 +124,8 @@
                         fromNativeStats.getDocumentRetrievalLatencyMs());
     }
 
-    /*
-     * Copy native Query stats to buiilder.
+    /**
+     * Copies native Delete stats to builder.
      *
      * @param fromNativeStats Stats copied from.
      * @param toStatsBuilder Stats copied to.
@@ -138,4 +140,28 @@
                 .setDeleteType(fromNativeStats.getDeleteType().getNumber())
                 .setDeletedDocumentCount(fromNativeStats.getNumDocumentsDeleted());
     }
+
+    /**
+     * Copies native {@link OptimizeStatsProto} to builder.
+     *
+     * @param fromNativeStats Stats copied from.
+     * @param toStatsBuilder Stats copied to.
+     */
+    static void copyNativeStats(
+            @NonNull OptimizeStatsProto fromNativeStats,
+            @NonNull OptimizeStats.Builder toStatsBuilder) {
+        Objects.requireNonNull(fromNativeStats);
+        Objects.requireNonNull(toStatsBuilder);
+        toStatsBuilder
+                .setNativeLatencyMillis(fromNativeStats.getLatencyMs())
+                .setDocumentStoreOptimizeLatencyMillis(
+                        fromNativeStats.getDocumentStoreOptimizeLatencyMs())
+                .setIndexRestorationLatencyMillis(fromNativeStats.getIndexRestorationLatencyMs())
+                .setOriginalDocumentCount(fromNativeStats.getNumOriginalDocuments())
+                .setDeletedDocumentCount(fromNativeStats.getNumDeletedDocuments())
+                .setExpiredDocumentCount(fromNativeStats.getNumExpiredDocuments())
+                .setStorageSizeBeforeBytes(fromNativeStats.getStorageSizeBefore())
+                .setStorageSizeAfterBytes(fromNativeStats.getStorageSizeAfter())
+                .setTimeSinceLastOptimizeMillis(fromNativeStats.getTimeSinceLastOptimizeMs());
+    }
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/OptimizeStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/OptimizeStats.java
new file mode 100644
index 0000000..83bd50f
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/OptimizeStats.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 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.external.localstorage.stats;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+
+import java.util.Objects;
+
+/**
+ * Class holds detailed stats for Optimize.
+ *
+ * @hide
+ */
+public final class OptimizeStats {
+    /**
+     * The status code returned by {@link AppSearchResult#getResultCode()} for the call or internal
+     * state.
+     */
+    @AppSearchResult.ResultCode private final int mStatusCode;
+
+    private final int mTotalLatencyMillis;
+    private final int mNativeLatencyMillis;
+
+    // Time used to optimize the document store in millis.
+    private final int mNativeDocumentStoreOptimizeLatencyMillis;
+
+    // Time used to restore the index in millis.
+    private final int mNativeIndexRestorationLatencyMillis;
+
+    // Number of documents before the optimization.
+    private final int mNativeOriginalDocumentCount;
+
+    // Number of documents deleted during the optimization.
+    private final int mNativeDeletedDocumentCount;
+
+    // Number of documents expired during the optimization.
+    private final int mNativeExpiredDocumentCount;
+
+    // Size of storage in bytes before the optimization.
+    private final long mNativeStorageSizeBeforeBytes;
+
+    // Size of storage in bytes after the optimization.
+    private final long mNativeStorageSizeAfterBytes;
+
+    // The amount of time in millis since the last optimization ran calculated using wall clock time
+    private final long mNativeTimeSinceLastOptimizeMillis;
+
+    OptimizeStats(@NonNull Builder builder) {
+        Objects.requireNonNull(builder);
+        mStatusCode = builder.mStatusCode;
+        mTotalLatencyMillis = builder.mTotalLatencyMillis;
+        mNativeLatencyMillis = builder.mNativeLatencyMillis;
+        mNativeDocumentStoreOptimizeLatencyMillis =
+                builder.mNativeDocumentStoreOptimizeLatencyMillis;
+        mNativeIndexRestorationLatencyMillis = builder.mNativeIndexRestorationLatencyMillis;
+        mNativeOriginalDocumentCount = builder.mNativeOriginalDocumentCount;
+        mNativeDeletedDocumentCount = builder.mNativeDeletedDocumentCount;
+        mNativeExpiredDocumentCount = builder.mNativeExpiredDocumentCount;
+        mNativeStorageSizeBeforeBytes = builder.mNativeStorageSizeBeforeBytes;
+        mNativeStorageSizeAfterBytes = builder.mNativeStorageSizeAfterBytes;
+        mNativeTimeSinceLastOptimizeMillis = builder.mNativeTimeSinceLastOptimizeMillis;
+    }
+
+    /** Returns status code for this optimization. */
+    @AppSearchResult.ResultCode
+    public int getStatusCode() {
+        return mStatusCode;
+    }
+
+    /** Returns total latency of this optimization in millis. */
+    public int getTotalLatencyMillis() {
+        return mTotalLatencyMillis;
+    }
+
+    /** Returns how much time in millis spent in the native code. */
+    public int getNativeLatencyMillis() {
+        return mNativeLatencyMillis;
+    }
+
+    /** Returns time used to optimize the document store in millis. */
+    public int getDocumentStoreOptimizeLatencyMillis() {
+        return mNativeDocumentStoreOptimizeLatencyMillis;
+    }
+
+    /** Returns time used to restore the index in millis. */
+    public int getIndexRestorationLatencyMillis() {
+        return mNativeIndexRestorationLatencyMillis;
+    }
+
+    /** Returns number of documents before the optimization. */
+    public int getOriginalDocumentCount() {
+        return mNativeOriginalDocumentCount;
+    }
+
+    /** Returns number of documents deleted during the optimization. */
+    public int getDeletedDocumentCount() {
+        return mNativeDeletedDocumentCount;
+    }
+
+    /** Returns number of documents expired during the optimization. */
+    public int getExpiredDocumentCount() {
+        return mNativeExpiredDocumentCount;
+    }
+
+    /** Returns size of storage in bytes before the optimization. */
+    public long getStorageSizeBeforeBytes() {
+        return mNativeStorageSizeBeforeBytes;
+    }
+
+    /** Returns size of storage in bytes after the optimization. */
+    public long getStorageSizeAfterBytes() {
+        return mNativeStorageSizeAfterBytes;
+    }
+
+    /**
+     * Returns the amount of time in millis since the last optimization ran calculated using wall
+     * clock time.
+     */
+    public long getTimeSinceLastOptimizeMillis() {
+        return mNativeTimeSinceLastOptimizeMillis;
+    }
+
+    /** Builder for {@link RemoveStats}. */
+    public static class Builder {
+        /**
+         * The status code returned by {@link AppSearchResult#getResultCode()} for the call or
+         * internal state.
+         */
+        @AppSearchResult.ResultCode int mStatusCode;
+
+        int mTotalLatencyMillis;
+        int mNativeLatencyMillis;
+        int mNativeDocumentStoreOptimizeLatencyMillis;
+        int mNativeIndexRestorationLatencyMillis;
+        int mNativeOriginalDocumentCount;
+        int mNativeDeletedDocumentCount;
+        int mNativeExpiredDocumentCount;
+        long mNativeStorageSizeBeforeBytes;
+        long mNativeStorageSizeAfterBytes;
+        long mNativeTimeSinceLastOptimizeMillis;
+
+        /** Sets the status code. */
+        @NonNull
+        public Builder setStatusCode(@AppSearchResult.ResultCode int statusCode) {
+            mStatusCode = statusCode;
+            return this;
+        }
+
+        /** Sets total latency in millis. */
+        @NonNull
+        public Builder setTotalLatencyMillis(int totalLatencyMillis) {
+            mTotalLatencyMillis = totalLatencyMillis;
+            return this;
+        }
+
+        /** Sets native latency in millis. */
+        @NonNull
+        public Builder setNativeLatencyMillis(int nativeLatencyMillis) {
+            mNativeLatencyMillis = nativeLatencyMillis;
+            return this;
+        }
+
+        /** Sets time used to optimize the document store. */
+        @NonNull
+        public Builder setDocumentStoreOptimizeLatencyMillis(
+                int documentStoreOptimizeLatencyMillis) {
+            mNativeDocumentStoreOptimizeLatencyMillis = documentStoreOptimizeLatencyMillis;
+            return this;
+        }
+
+        /** Sets time used to restore the index. */
+        @NonNull
+        public Builder setIndexRestorationLatencyMillis(int indexRestorationLatencyMillis) {
+            mNativeIndexRestorationLatencyMillis = indexRestorationLatencyMillis;
+            return this;
+        }
+
+        /** Sets number of documents before the optimization. */
+        @NonNull
+        public Builder setOriginalDocumentCount(int originalDocumentCount) {
+            mNativeOriginalDocumentCount = originalDocumentCount;
+            return this;
+        }
+
+        /** Sets number of documents deleted during the optimization. */
+        @NonNull
+        public Builder setDeletedDocumentCount(int deletedDocumentCount) {
+            mNativeDeletedDocumentCount = deletedDocumentCount;
+            return this;
+        }
+
+        /** Sets number of documents expired during the optimization. */
+        @NonNull
+        public Builder setExpiredDocumentCount(int expiredDocumentCount) {
+            mNativeExpiredDocumentCount = expiredDocumentCount;
+            return this;
+        }
+
+        /** Sets Storage size in bytes before optimization. */
+        @NonNull
+        public Builder setStorageSizeBeforeBytes(long storageSizeBeforeBytes) {
+            mNativeStorageSizeBeforeBytes = storageSizeBeforeBytes;
+            return this;
+        }
+
+        /** Sets storage size in bytes after optimization. */
+        @NonNull
+        public Builder setStorageSizeAfterBytes(long storageSizeAfterBytes) {
+            mNativeStorageSizeAfterBytes = storageSizeAfterBytes;
+            return this;
+        }
+
+        /**
+         * Sets the amount the time since the last optimize ran calculated using wall clock time.
+         */
+        @NonNull
+        public Builder setTimeSinceLastOptimizeMillis(long timeSinceLastOptimizeMillis) {
+            mNativeTimeSinceLastOptimizeMillis = timeSinceLastOptimizeMillis;
+            return this;
+        }
+
+        /** Creates a {@link OptimizeStats}. */
+        @NonNull
+        public OptimizeStats build() {
+            return new OptimizeStats(/* builder= */ this);
+        }
+    }
+}
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
index 2cbce10..fdf6a00 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -32,6 +32,7 @@
 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.InitializeStats;
+import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
 import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
 import com.android.server.appsearch.external.localstorage.stats.SearchStats;
@@ -145,7 +146,7 @@
     }
 
     @Override
-    public void logStats(@NonNull InitializeStats stats) throws AppSearchException {
+    public void logStats(@NonNull InitializeStats stats) {
         Objects.requireNonNull(stats);
         synchronized (mLock) {
             if (shouldLogForTypeLocked(CallStats.CALL_TYPE_INITIALIZE)) {
@@ -155,7 +156,7 @@
     }
 
     @Override
-    public void logStats(@NonNull SearchStats stats) throws AppSearchException {
+    public void logStats(@NonNull SearchStats stats) {
         Objects.requireNonNull(stats);
         synchronized (mLock) {
             if (shouldLogForTypeLocked(CallStats.CALL_TYPE_SEARCH)) {
@@ -165,10 +166,20 @@
     }
 
     @Override
-    public void logStats(@NonNull RemoveStats stats) throws AppSearchException {
+    public void logStats(@NonNull RemoveStats stats) {
         // TODO(b/173532925): Log stats
     }
 
+    @Override
+    public void logStats(@NonNull OptimizeStats stats) {
+        Objects.requireNonNull(stats);
+        synchronized (mLock) {
+            if (shouldLogForTypeLocked(CallStats.CALL_TYPE_OPTIMIZE)) {
+                logStatsImplLocked(stats);
+            }
+        }
+    }
+
     /**
      * Removes cached UID for package.
      *
@@ -326,6 +337,27 @@
                 stats.getResetStatusCode());
     }
 
+    @GuardedBy("mLock")
+    private void logStatsImplLocked(@NonNull OptimizeStats stats) {
+        mLastPushTimeMillisLocked = SystemClock.elapsedRealtime();
+        ExtraStats extraStats = createExtraStatsLocked(/*packageName=*/ null,
+                CallStats.CALL_TYPE_OPTIMIZE);
+        AppSearchStatsLog.write(AppSearchStatsLog.APP_SEARCH_OPTIMIZE_STATS_REPORTED,
+                extraStats.mSamplingInterval,
+                extraStats.mSkippedSampleCount,
+                stats.getStatusCode(),
+                stats.getTotalLatencyMillis(),
+                stats.getNativeLatencyMillis(),
+                stats.getDocumentStoreOptimizeLatencyMillis(),
+                stats.getIndexRestorationLatencyMillis(),
+                stats.getOriginalDocumentCount(),
+                stats.getDeletedDocumentCount(),
+                stats.getExpiredDocumentCount(),
+                stats.getStorageSizeBeforeBytes(),
+                stats.getStorageSizeAfterBytes(),
+                stats.getTimeSinceLastOptimizeMillis());
+    }
+
     /**
      * Calculate the hash code as an integer by returning the last four bytes of its MD5.
      *
@@ -464,15 +496,19 @@
                 return mConfig.getCachedSamplingIntervalForBatchCallStats();
             case CallStats.CALL_TYPE_PUT_DOCUMENT:
                 return mConfig.getCachedSamplingIntervalForPutDocumentStats();
-            case CallStats.CALL_TYPE_UNKNOWN:
             case CallStats.CALL_TYPE_INITIALIZE:
+                return mConfig.getCachedSamplingIntervalForInitializeStats();
+            case CallStats.CALL_TYPE_SEARCH:
+                return mConfig.getCachedSamplingIntervalForSearchStats();
+            case CallStats.CALL_TYPE_GLOBAL_SEARCH:
+                return mConfig.getCachedSamplingIntervalForGlobalSearchStats();
+            case CallStats.CALL_TYPE_OPTIMIZE:
+                return mConfig.getCachedSamplingIntervalForOptimizeStats();
+            case CallStats.CALL_TYPE_UNKNOWN:
             case CallStats.CALL_TYPE_SET_SCHEMA:
             case CallStats.CALL_TYPE_GET_DOCUMENT:
             case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_ID:
-            case CallStats.CALL_TYPE_SEARCH:
-            case CallStats.CALL_TYPE_OPTIMIZE:
             case CallStats.CALL_TYPE_FLUSH:
-            case CallStats.CALL_TYPE_GLOBAL_SEARCH:
             case CallStats.CALL_TYPE_REMOVE_DOCUMENT_BY_SEARCH:
                 // TODO(b/173532925) Some of them above will have dedicated sampling ratio config
             default:
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index 6555107..a81d7d80 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-c7387d9b58726a23a0608a9365fb3a1b57b7b8a1
+Ie04f1ecc033faae8085afcb51eb9e40a298998d5
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 98e963e..86fa06c 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -2052,10 +2052,11 @@
                     + " -- package not allowed to start");
             return;
         }
+        final int callerProcState = mActivityManagerInternal.getUidProcessState(callingUid);
         removeLocked(operation, directReceiver, REMOVE_REASON_UNDEFINED);
         incrementAlarmCount(a.uid);
         setImplLocked(a);
-        MetricsHelper.pushAlarmScheduled(a);
+        MetricsHelper.pushAlarmScheduled(a, callerProcState);
     }
 
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
index 4e7311f..4c2f8d1 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/MetricsHelper.java
@@ -22,6 +22,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ALARM_SCHEDULED__EXACT_ALARM_ALLOWED_REASON__PERMISSION;
 import static com.android.server.alarm.AlarmManagerService.INDEFINITE_DELAY;
 
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.StatsManager;
 import android.content.Context;
@@ -93,7 +94,7 @@
         }
     }
 
-    static void pushAlarmScheduled(Alarm a) {
+    static void pushAlarmScheduled(Alarm a, int callerProcState) {
         FrameworkStatsLog.write(
                 FrameworkStatsLog.ALARM_SCHEDULED,
                 a.uid,
@@ -103,7 +104,8 @@
                 a.alarmClock != null,
                 a.repeatInterval != 0,
                 reasonToStatsReason(a.mExactAllowReason),
-                AlarmManagerService.isRtc(a.type));
+                AlarmManagerService.isRtc(a.type),
+                ActivityManager.processStateAmToProto(callerProcState));
     }
 
     static void pushAlarmBatchDelivered(int numAlarms, int wakeups) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 3fa1c12..90baa8e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -116,6 +116,16 @@
     @VisibleForTesting
     int mVerb;
     private boolean mCancelled;
+    /**
+     * True if the previous job on this context successfully finished (ie. called jobFinished or
+     * dequeueWork with no work left).
+     */
+    private boolean mPreviousJobHadSuccessfulFinish;
+    /**
+     * The last time a job on this context didn't finish successfully, in the elapsed realtime
+     * timebase.
+     */
+    private long mLastUnsuccessfulFinishElapsed;
 
     /**
      * All the information maintained about the job currently being executed.
@@ -439,7 +449,9 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                assertCallerLocked(cb);
+                if (!assertCallerLocked(cb)) {
+                    return null;
+                }
                 if (mVerb == VERB_STOPPING || mVerb == VERB_FINISHED) {
                     // This job is either all done, or on its way out.  Either way, it
                     // should not dispatch any more work.  We will pick up any remaining
@@ -465,7 +477,11 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                assertCallerLocked(cb);
+                if (!assertCallerLocked(cb)) {
+                    // Return true instead of false here so we don't just kick the
+                    // Exception-throwing-can down the road to JobParameters.completeWork >:(
+                    return true;
+                }
                 return mRunningJob.completeWorkLocked(workId);
             }
         } finally {
@@ -554,18 +570,34 @@
         return true;
     }
 
-    private void assertCallerLocked(JobCallback cb) {
+    /**
+     * Will throw a {@link SecurityException} if the callback is not for the currently running job,
+     * but may decide not to throw an exception if the call from the previous job appears to be an
+     * accident.
+     *
+     * @return true if the callback is for the current job, false otherwise
+     */
+    private boolean assertCallerLocked(JobCallback cb) {
         if (!verifyCallerLocked(cb)) {
+            final long nowElapsed = sElapsedRealtimeClock.millis();
+            if (!mPreviousJobHadSuccessfulFinish
+                    && (nowElapsed - mLastUnsuccessfulFinishElapsed) < 15_000L) {
+                // Don't punish apps for race conditions
+                return false;
+            }
+            // It's been long enough that the app should really not be calling into JS for the
+            // stopped job.
             StringBuilder sb = new StringBuilder(128);
             sb.append("Caller no longer running");
             if (cb.mStoppedReason != null) {
                 sb.append(", last stopped ");
-                TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
+                TimeUtils.formatDuration(nowElapsed - cb.mStoppedTime, sb);
                 sb.append(" because: ");
                 sb.append(cb.mStoppedReason);
             }
             throw new SecurityException(sb.toString());
         }
+        return true;
     }
 
     /**
@@ -911,6 +943,11 @@
         applyStoppedReasonLocked(reason);
         completedJob = mRunningJob;
         final int internalStopReason = mParams.getInternalStopReasonCode();
+        mPreviousJobHadSuccessfulFinish =
+                (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
+        if (!mPreviousJobHadSuccessfulFinish) {
+            mLastUnsuccessfulFinishElapsed = sElapsedRealtimeClock.millis();
+        }
         mJobPackageTracker.noteInactive(completedJob, internalStopReason, reason);
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                 completedJob.getSourceUid(), null, completedJob.getBatteryName(),
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6c6e547..df670f4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3202,6 +3202,59 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
   }
 
+  public final class TaskFragmentAppearedInfo implements android.os.Parcelable {
+    method @NonNull public android.view.SurfaceControl getLeash();
+    method @NonNull public android.window.TaskFragmentInfo getTaskFragmentInfo();
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentAppearedInfo> CREATOR;
+  }
+
+  public final class TaskFragmentCreationParams implements android.os.Parcelable {
+    method @NonNull public android.os.IBinder getFragmentToken();
+    method @NonNull public android.graphics.Rect getInitialBounds();
+    method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
+    method @NonNull public android.os.IBinder getOwnerToken();
+    method public int getWindowingMode();
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
+  }
+
+  public static final class TaskFragmentCreationParams.Builder {
+    ctor public TaskFragmentCreationParams.Builder(@NonNull android.window.TaskFragmentOrganizerToken, @NonNull android.os.IBinder, @NonNull android.os.IBinder);
+    method @NonNull public android.window.TaskFragmentCreationParams build();
+    method @NonNull public android.window.TaskFragmentCreationParams.Builder setInitialBounds(@NonNull android.graphics.Rect);
+    method @NonNull public android.window.TaskFragmentCreationParams.Builder setWindowingMode(int);
+  }
+
+  public final class TaskFragmentInfo implements android.os.Parcelable {
+    method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
+    method @NonNull public java.util.List<android.os.IBinder> getActivities();
+    method @NonNull public android.content.res.Configuration getConfiguration();
+    method @NonNull public android.os.IBinder getFragmentToken();
+    method @NonNull public android.graphics.Point getPositionInParent();
+    method @NonNull public android.window.WindowContainerToken getToken();
+    method public int getWindowingMode();
+    method public boolean hasRunningActivity();
+    method public boolean isEmpty();
+    method public boolean isVisible();
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
+  }
+
+  public class TaskFragmentOrganizer extends android.window.WindowOrganizer {
+    ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
+    method @NonNull public java.util.concurrent.Executor getExecutor();
+    method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
+    method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentAppearedInfo);
+    method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+    method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
+    method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
+    method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
+    method @CallSuper public void registerOrganizer();
+    method @CallSuper public void unregisterOrganizer();
+  }
+
+  public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
+  }
+
   public class TaskOrganizer extends android.window.WindowOrganizer {
     ctor public TaskOrganizer();
     method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
@@ -3230,9 +3283,13 @@
 
   public final class WindowContainerTransaction implements android.os.Parcelable {
     ctor public WindowContainerTransaction();
+    method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams);
+    method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken);
     method public int describeContents();
     method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder);
+    method @NonNull public android.window.WindowContainerTransaction reparentChildren(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken);
     method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
     method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
@@ -3240,12 +3297,14 @@
     method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
+    method @NonNull public android.window.WindowContainerTransaction setErrorCallbackToken(@NonNull android.os.IBinder);
     method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]);
     method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
     method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
     method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
+    method @NonNull public android.window.WindowContainerTransaction startActivityInTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder, @NonNull android.content.Intent, @Nullable android.os.Bundle);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerTransaction> CREATOR;
   }
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 6c001f3..26c83ee 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -112,6 +112,8 @@
     srcs: [
         "android/os/Temperature.aidl",
         "android/os/CoolingDevice.aidl",
+        "android/os/IHintManager.aidl",
+        "android/os/IHintSession.aidl",
         "android/os/IThermalEventListener.aidl",
         "android/os/IThermalStatusListener.aidl",
         "android/os/IThermalService.aidl",
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index db5dcc5..643020a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1539,6 +1539,17 @@
         getApplication().dispatchActivityPostDestroyed(this);
     }
 
+    private void dispatchActivityConfigurationChanged() {
+        getApplication().dispatchActivityConfigurationChanged(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivityConfigurationChanged(this);
+            }
+        }
+    }
+
     private Object[] collectActivityLifecycleCallbacks() {
         Object[] callbacks = null;
         synchronized (mActivityLifecycleCallbacks) {
@@ -3028,6 +3039,8 @@
             // view changes from above.
             mActionBar.onConfigurationChanged(newConfig);
         }
+
+        dispatchActivityConfigurationChanged();
     }
 
     /**
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 618eda8..a1eab65 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -205,6 +205,13 @@
          */
         default void onActivityPostDestroyed(@NonNull Activity activity) {
         }
+
+        /**
+         * Called when the Activity configuration was changed.
+         * @hide
+         */
+        default void onActivityConfigurationChanged(@NonNull Activity activity) {
+        }
     }
 
     /**
@@ -554,6 +561,16 @@
         }
     }
 
+    /* package */ void dispatchActivityConfigurationChanged(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityConfigurationChanged(
+                        activity);
+            }
+        }
+    }
+
     @UnsupportedAppUsage
     private Object[] collectActivityLifecycleCallbacks() {
         Object[] callbacks = null;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 249a606..7114d73 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -26,6 +26,7 @@
 import android.annotation.Nullable;
 import android.annotation.UiContext;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -60,7 +61,6 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.content.AttributionSource;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -3123,6 +3123,7 @@
             mForceDisplayOverrideInResources = container.mForceDisplayOverrideInResources;
             mIsConfigurationBasedContext = container.mIsConfigurationBasedContext;
             mContextType = container.mContextType;
+            mContentCaptureOptions = container.mContentCaptureOptions;
         } else {
             mBasePackageName = packageInfo.mPackageName;
             ApplicationInfo ainfo = packageInfo.getApplicationInfo();
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 871d48b..32ea41b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -158,7 +158,6 @@
 import android.os.IBinder;
 import android.os.IDumpstate;
 import android.os.IHardwarePropertiesManager;
-import android.os.IHintManager;
 import android.os.IPowerManager;
 import android.os.IRecoverySystem;
 import android.os.ISystemUpdateManager;
@@ -600,10 +599,7 @@
             @Override
             public PerformanceHintManager createService(ContextImpl ctx)
                     throws ServiceNotFoundException {
-                IBinder hintBinder = ServiceManager.getServiceOrThrow(
-                        Context.PERFORMANCE_HINT_SERVICE);
-                IHintManager hintService = IHintManager.Stub.asInterface(hintBinder);
-                return new PerformanceHintManager(hintService);
+                return PerformanceHintManager.create();
             }});
 
         registerService(Context.RECOVERY_SERVICE, RecoverySystem.class,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 549ab6e..0e04ad3 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -10997,6 +10997,16 @@
      * {@link #EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT} in the provisioning parameters.
      * In that case the device owner's control will be limited do denying these permissions.
      * <p>
+     * NOTE: On devices running {@link android.os.Build.VERSION_CODES#S} and above, control over
+     * the following permissions are restricted for managed profile owners:
+     * <ul>
+     *    <li>Manifest.permission.READ_SMS</li>
+     * </ul>
+     * <p>
+     * A managed profile owner may not grant these permissions (i.e. call this method with any of
+     * the permissions listed above and {@code grantState} of
+     * {@code #PERMISSION_GRANT_STATE_GRANTED}), but may deny them.
+     * <p>
      * Attempts by the admin to grant these permissions, when the admin is restricted from doing
      * so, will be silently ignored (no exception will be thrown).
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2ed00b5..d4fa2d5 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3699,6 +3699,17 @@
     public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
             "android.hardware.keystore.app_attest_key";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * is opted-in to receive per-app compatibility overrides that are applied in
+     * {@link com.android.server.compat.overrides.AppCompatOverridesService}.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_APP_COMPAT_OVERRIDES =
+            "android.software.app_compat_overrides";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index debc074..d69a9e1 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -48,4 +48,8 @@
 
     void suppressIndividualSensorPrivacyReminders(int userId, int sensor, IBinder token,
             boolean suppress);
+
+    void addUserGlobalIndividualSensorPrivacyListener(int sensor, in ISensorPrivacyListener listener);
+
+    void removeUserGlobalIndividualSensorPrivacyListener(int sensor, in ISensorPrivacyListener listener);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index b7d95e7..4526ab77 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
@@ -247,8 +248,7 @@
     @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
     public void addSensorPrivacyListener(@Sensors.Sensor int sensor,
             @NonNull OnSensorPrivacyChangedListener listener) {
-        addSensorPrivacyListener(sensor, mContext.getUserId(), mContext.getMainExecutor(),
-                listener);
+        addSensorPrivacyListener(sensor, mContext.getMainExecutor(), listener);
     }
 
     /**
@@ -283,7 +283,25 @@
     @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
     public void addSensorPrivacyListener(@Sensors.Sensor int sensor, @NonNull Executor executor,
             @NonNull OnSensorPrivacyChangedListener listener) {
-        addSensorPrivacyListener(sensor, mContext.getUserId(), executor, listener);
+        Pair<OnSensorPrivacyChangedListener, Integer> key = new Pair<>(listener, sensor);
+        synchronized (mIndividualListeners) {
+            ISensorPrivacyListener iListener = mIndividualListeners.get(key);
+            if (iListener == null) {
+                iListener = new ISensorPrivacyListener.Stub() {
+                    @Override
+                    public void onSensorPrivacyChanged(boolean enabled) {
+                        executor.execute(() -> listener.onSensorPrivacyChanged(sensor, enabled));
+                    }
+                };
+                mIndividualListeners.put(key, iListener);
+            }
+
+            try {
+                mService.addUserGlobalIndividualSensorPrivacyListener(sensor, iListener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
     }
 
     /**
@@ -361,7 +379,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
     public boolean isSensorPrivacyEnabled(@Sensors.Sensor int sensor) {
-        return isSensorPrivacyEnabled(sensor, mContext.getUserId());
+        return isSensorPrivacyEnabled(sensor, getCurrentUserId());
     }
 
     /**
@@ -392,7 +410,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void setSensorPrivacy(@Sources.Source int source, @Sensors.Sensor int sensor,
             boolean enable) {
-        setSensorPrivacy(source, sensor, enable, mContext.getUserId());
+        setSensorPrivacy(source, sensor, enable, getCurrentUserId());
     }
 
     /**
@@ -428,7 +446,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void setSensorPrivacyForProfileGroup(@Sources.Source int source,
             @Sensors.Sensor int sensor, boolean enable) {
-        setSensorPrivacyForProfileGroup(source , sensor, enable, mContext.getUserId());
+        setSensorPrivacyForProfileGroup(source , sensor, enable, getCurrentUserId());
     }
 
     /**
@@ -463,7 +481,7 @@
     @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
     public void suppressSensorPrivacyReminders(int sensor,
             boolean suppress) {
-        suppressSensorPrivacyReminders(sensor, suppress, mContext.getUserId());
+        suppressSensorPrivacyReminders(sensor, suppress, getCurrentUserId());
     }
 
     /**
@@ -590,4 +608,13 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    private int getCurrentUserId() {
+        try {
+            return ActivityManager.getService().getCurrentUserId();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return 0;
+    }
 }
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index c2a2c4c..3c3ba59 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -169,31 +169,6 @@
     }
 
     /**
-     * Request authentication of a crypto object. This call operates the face recognition hardware
-     * and starts capturing images. It terminates when
-     * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
-     * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
-     * which point the object is no longer valid. The operation can be canceled by using the
-     * provided cancel object.
-     *
-     * @param crypto   object associated with the call or null if none required.
-     * @param cancel   an object that can be used to cancel authentication
-     * @param callback an object to receive authentication events
-     * @param handler  an optional handler to handle callback events
-     * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
-     *                                  by
-     *                                  <a href="{@docRoot}training/articles/keystore.html">Android
-     *                                  Keystore facility</a>.
-     * @throws IllegalStateException    if the crypto primitive is not initialized.
-     * @hide
-     */
-    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
-    public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull AuthenticationCallback callback, @Nullable Handler handler) {
-        authenticate(crypto, cancel, callback, handler, mContext.getUserId());
-    }
-
-    /**
      * Use the provided handler thread for events.
      */
     private void useHandler(Handler handler) {
@@ -224,8 +199,10 @@
      * @throws IllegalStateException    if the crypto primitive is not initialized.
      * @hide
      */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
     public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
-            @NonNull AuthenticationCallback callback, @Nullable Handler handler, int userId) {
+            @NonNull AuthenticationCallback callback, @Nullable Handler handler, int userId,
+            boolean isKeyguardBypassEnabled) {
         if (callback == null) {
             throw new IllegalArgumentException("Must supply an authentication callback");
         }
@@ -247,7 +224,7 @@
                 final long operationId = crypto != null ? crypto.getOpId() : 0;
                 Trace.beginSection("FaceManager#authenticate");
                 mService.authenticate(mToken, operationId, userId, mServiceReceiver,
-                        mContext.getOpPackageName());
+                        mContext.getOpPackageName(), isKeyguardBypassEnabled);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception while authenticating: ", e);
                 if (callback != null) {
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index b9a49c6..db02a0ef 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -46,7 +46,7 @@
 
     // Authenticate the given sessionId with a face
     void authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
-            String opPackageName);
+            String opPackageName, boolean isKeyguardBypassEnabled);
 
     // Uses the face hardware to detect for the presence of a face, without giving details
     // about accept/reject/lockout.
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 308e6d5..0257408 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -693,6 +693,9 @@
      * <p>
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * monitor_storage}
+     * <p>
+     * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or
+     * {@link MediaStore} offer better performance.
      *
      * @see #getExternalStorageState()
      * @see #isExternalStorageRemovable()
@@ -993,6 +996,9 @@
      * </p>
      * {@sample development/samples/ApiDemos/src/com/example/android/apis/content/ExternalStorage.java
      * public_picture}
+     * <p>
+     * Note that alternatives such as {@link Context#getExternalFilesDir(String)} or
+     * {@link MediaStore} offer better performance.
      *
      * @param type The type of storage directory to return. Should be one of
      *            {@link #DIRECTORY_MUSIC}, {@link #DIRECTORY_PODCASTS},
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 6791844..a75b5ef 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -24,24 +24,23 @@
 import com.android.internal.util.Preconditions;
 
 import java.io.Closeable;
-import java.util.ArrayList;
 
 /** The PerformanceHintManager allows apps to send performance hint to system. */
 @SystemService(Context.PERFORMANCE_HINT_SERVICE)
 public final class PerformanceHintManager {
-    private static final String TAG = "PerformanceHintManager";
-    private final IHintManager mService;
-    // HAL preferred update rate
-    private final long mPreferredRate;
+    private final long mNativeManagerPtr;
 
     /** @hide */
-    public PerformanceHintManager(IHintManager service) {
-        mService = service;
-        try {
-            mPreferredRate = mService.getHintSessionPreferredRate();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
+    public static PerformanceHintManager create() throws ServiceManager.ServiceNotFoundException {
+        long nativeManagerPtr = nativeAcquireManager();
+        if (nativeManagerPtr == 0) {
+            throw new ServiceManager.ServiceNotFoundException(Context.PERFORMANCE_HINT_SERVICE);
         }
+        return new PerformanceHintManager(nativeManagerPtr);
+    }
+
+    private PerformanceHintManager(long nativeManagerPtr) {
+        mNativeManagerPtr = nativeManagerPtr;
     }
 
     /**
@@ -57,16 +56,13 @@
      */
     @Nullable
     public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) {
-        try {
-            IBinder token = new Binder();
-            IHintSession session = mService.createHintSession(token, tids,
-                    initialTargetWorkDurationNanos);
-            if (session == null) return null;
-            return new Session(session, sNanoClock, mPreferredRate,
-                    initialTargetWorkDurationNanos);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        Preconditions.checkNotNull(tids, "tids cannot be null");
+        Preconditions.checkArgumentPositive(initialTargetWorkDurationNanos,
+                "the hint target duration should be positive.");
+        long nativeSessionPtr = nativeCreateSession(mNativeManagerPtr, tids,
+                initialTargetWorkDurationNanos);
+        if (nativeSessionPtr == 0) return null;
+        return new Session(nativeSessionPtr);
     }
 
     /**
@@ -75,7 +71,7 @@
      * @return the preferred update rate supported by device software.
      */
     public long getPreferredUpdateRateNanos() {
-        return mPreferredRate;
+        return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr);
     }
 
     /**
@@ -101,28 +97,21 @@
      * <p>All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.</p>
      */
     public static class Session implements Closeable {
-        private final IHintSession mSession;
-        private final NanoClock mElapsedRealtimeClock;
-        // Target duration for choosing update rate
-        private long mTargetDurationInNanos;
-        // HAL preferred update rate
-        private long mPreferredRate;
-        // Last update timestamp
-        private long mLastUpdateTimeStamp = -1L;
-        // Cached samples
-        private final ArrayList<Long> mActualDurationNanos;
-        private final ArrayList<Long> mTimeStampNanos;
+        private long mNativeSessionPtr;
 
         /** @hide */
-        public Session(IHintSession session, NanoClock elapsedRealtimeClock, long preferredRate,
-                long durationNanos) {
-            mSession = session;
-            mElapsedRealtimeClock = elapsedRealtimeClock;
-            mTargetDurationInNanos = durationNanos;
-            mPreferredRate = preferredRate;
-            mActualDurationNanos = new ArrayList<Long>();
-            mTimeStampNanos = new ArrayList<Long>();
-            mLastUpdateTimeStamp = mElapsedRealtimeClock.nanos();
+        public Session(long nativeSessionPtr) {
+            mNativeSessionPtr = nativeSessionPtr;
+        }
+
+        /** @hide */
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                close();
+            } finally {
+                super.finalize();
+            }
         }
 
         /**
@@ -133,19 +122,7 @@
         public void updateTargetWorkDuration(long targetDurationNanos) {
             Preconditions.checkArgumentPositive(targetDurationNanos, "the hint target duration"
                     + " should be positive.");
-            try {
-                mSession.updateTargetWorkDuration(targetDurationNanos);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            mTargetDurationInNanos = targetDurationNanos;
-            /**
-             * Most of the workload is target_duration dependent, so now clear the cached samples
-             * as they are most likely obsolete.
-             */
-            mActualDurationNanos.clear();
-            mTimeStampNanos.clear();
-            mLastUpdateTimeStamp = mElapsedRealtimeClock.nanos();
+            nativeUpdateTargetWorkDuration(mNativeSessionPtr, targetDurationNanos);
         }
 
         /**
@@ -161,38 +138,7 @@
         public void reportActualWorkDuration(long actualDurationNanos) {
             Preconditions.checkArgumentPositive(actualDurationNanos, "the actual duration should"
                     + " be positive.");
-            final long now = mElapsedRealtimeClock.nanos();
-            mActualDurationNanos.add(actualDurationNanos);
-            mTimeStampNanos.add(now);
-
-            /**
-             * Use current sample to determine the rate limit. We can pick a shorter rate limit
-             * if any sample underperformed, however, it could be the lower level system is slow
-             * to react. So here we explicitly choose the rate limit with the latest sample.
-             */
-            long rateLimit =
-                    actualDurationNanos > mTargetDurationInNanos ? mPreferredRate
-                            : 10 * mPreferredRate;
-
-            if (now - mLastUpdateTimeStamp <= rateLimit) {
-                return;
-            }
-            Preconditions.checkState(mActualDurationNanos.size() == mTimeStampNanos.size());
-            final int size = mActualDurationNanos.size();
-            long[] actualDurationArray = new long[size];
-            long[] timeStampArray = new long[size];
-            for (int i = 0; i < size; i++) {
-                actualDurationArray[i] = mActualDurationNanos.get(i);
-                timeStampArray[i] = mTimeStampNanos.get(i);
-            }
-            try {
-                mSession.reportActualWorkDuration(actualDurationArray, timeStampArray);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-            mActualDurationNanos.clear();
-            mTimeStampNanos.clear();
-            mLastUpdateTimeStamp = now;
+            nativeReportActualWorkDuration(mNativeSessionPtr, actualDurationNanos);
         }
 
         /**
@@ -201,26 +147,20 @@
          * <p>Once called, you should not call anything else on this object.</p>
          */
         public void close() {
-            try {
-                mSession.close();
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
+            if (mNativeSessionPtr != 0) {
+                nativeCloseSession(mNativeSessionPtr);
+                mNativeSessionPtr = 0;
             }
         }
     }
 
-    /**
-     * The interface is to make the FakeClock for testing.
-     * @hide
-     */
-    public interface NanoClock {
-        /** Gets the current nanosecond instant of the clock. */
-        long nanos();
-    }
-
-    private static final NanoClock sNanoClock = new NanoClock() {
-        public long nanos() {
-            return SystemClock.elapsedRealtimeNanos();
-        }
-    };
+    private static native long nativeAcquireManager();
+    private static native long nativeGetPreferredUpdateRateNanos(long nativeManagerPtr);
+    private static native long nativeCreateSession(long nativeManagerPtr,
+            int[] tids, long initialTargetWorkDurationNanos);
+    private static native void nativeUpdateTargetWorkDuration(long nativeSessionPtr,
+            long targetDurationNanos);
+    private static native void nativeReportActualWorkDuration(long nativeSessionPtr,
+            long actualDurationNanos);
+    private static native void nativeCloseSession(long nativeSessionPtr);
 }
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index c8cbc51..ddb6533 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -102,8 +102,6 @@
     /** @hide */
     public static final long TRACE_TAG_RRO = 1L << 26;
     /** @hide */
-    public static final long TRACE_TAG_SYSPROP = 1L << 27;
-    /** @hide */
     public static final long TRACE_TAG_APEX_MANAGER = 1L << 18;
 
     private static final long TRACE_TAG_NOT_READY = 1L << 63;
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index a435239..e3bb589 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -72,8 +72,7 @@
 @SystemApi
 public abstract class HotwordDetectionService extends Service {
     private static final String TAG = "HotwordDetectionService";
-    // TODO (b/177502877): Set the Debug flag to false before shipping.
-    private static final boolean DBG = true;
+    private static final boolean DBG = false;
 
     private static final long UPDATE_TIMEOUT_MILLIS = 5000;
 
@@ -151,9 +150,7 @@
         @Override
         public void updateState(PersistableBundle options, SharedMemory sharedMemory,
                 IRemoteCallback callback) throws RemoteException {
-            if (DBG) {
-                Log.d(TAG, "#updateState");
-            }
+            Log.v(TAG, "#updateState" + (callback != null ? " with callback" : ""));
             HotwordDetectionService.this.onUpdateStateInternal(
                     options,
                     sharedMemory,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index fb540b1..02294e5 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -47,7 +47,7 @@
  **/
 class SoftwareHotwordDetector extends AbstractHotwordDetector {
     private static final String TAG = SoftwareHotwordDetector.class.getSimpleName();
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private final IVoiceInteractionManagerService mManagerService;
     private final HotwordDetector.Callback mCallback;
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index fe1fcfc..1111c28 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -73,6 +73,7 @@
 import android.view.InputEventReceiver;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.MotionEvent;
 import android.view.PixelCopy;
 import android.view.Surface;
@@ -224,10 +225,9 @@
         final ClientWindowFrames mWinFrames = new ClientWindowFrames();
         final Rect mDispatchedContentInsets = new Rect();
         final Rect mDispatchedStableInsets = new Rect();
-        final Rect mFinalSystemInsets = new Rect();
-        final Rect mFinalStableInsets = new Rect();
         DisplayCutout mDispatchedDisplayCutout = DisplayCutout.NO_CUTOUT;
         final InsetsState mInsetsState = new InsetsState();
+        final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
         final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
         final MergedConfiguration mMergedConfiguration = new MergedConfiguration();
         private final Point mSurfaceSize = new Point();
@@ -995,8 +995,8 @@
                         InputChannel inputChannel = new InputChannel();
 
                         if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
-                                mDisplay.getDisplayId(), mInsetsState, inputChannel, mInsetsState,
-                                mTempControls) < 0) {
+                                mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel,
+                                mInsetsState, mTempControls) < 0) {
                             Log.w(TAG, "Failed to add window while updating wallpaper surface.");
                             return;
                         }
@@ -1033,6 +1033,9 @@
                                     .build();
                             updateSurfaceDimming();
                         }
+                        // Propagate transform hint from WM so we can use the right hint for the
+                        // first frame.
+                        mBbqSurfaceControl.setTransformHint(mSurfaceControl.getTransformHint());
                         Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x,
                                 mSurfaceSize.y, mFormat);
                         // If blastSurface == null that means it hasn't changed since the last
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index e1a4402..0257e55 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -31,6 +31,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Path;
@@ -873,18 +874,147 @@
     }
 
     /**
+     * Gets the index of the given display unique id in {@link R.array#config_displayUniqueIdArray}
+     * which is used to get the related cutout configs for that display.
+     *
+     * For multi-display device, {@link R.array#config_displayUniqueIdArray} should be set for each
+     * display if there are different type of cutouts on each display.
+     * For single display device, {@link R.array#config_displayUniqueIdArray} should not to be set
+     * and the system will load the default configs for main built-in display.
+     */
+    private static int getDisplayCutoutConfigIndex(Resources res, String displayUniqueId) {
+        int index = -1;
+        if (displayUniqueId == null || displayUniqueId.isEmpty()) {
+            return index;
+        }
+        final String[] ids = res.getStringArray(R.array.config_displayUniqueIdArray);
+        final int size = ids.length;
+        for (int i = 0; i < size; i++) {
+            if (displayUniqueId.equals(ids[i])) {
+                index = i;
+                break;
+            }
+        }
+        return index;
+    }
+
+    /**
+     * Gets the display cutout by the given display unique id.
+     *
+     * Loads the default config {@link R.string#config_mainBuiltInDisplayCutout) if
+     * {@link R.array#config_displayUniqueIdArray} is not set.
+     */
+    private static String getDisplayCutoutPath(Resources res, String displayUniqueId) {
+        final int index = getDisplayCutoutConfigIndex(res, displayUniqueId);
+        final String[] array = res.getStringArray(R.array.config_displayCutoutPathArray);
+        if (index >= 0 && index < array.length) {
+            return array[index];
+        }
+        return res.getString(R.string.config_mainBuiltInDisplayCutout);
+    }
+
+    /**
+     * Gets the display cutout approximation rect by the given display unique id.
+     *
+     * Loads the default config {@link R.string#config_mainBuiltInDisplayCutoutRectApproximation} if
+     * {@link R.array#config_displayUniqueIdArray} is not set.
+     */
+    private static String getDisplayCutoutApproximationRect(Resources res, String displayUniqueId) {
+        final int index = getDisplayCutoutConfigIndex(res, displayUniqueId);
+        final String[] array = res.getStringArray(
+                R.array.config_displayCutoutApproximationRectArray);
+        if (index >= 0 && index < array.length) {
+            return array[index];
+        }
+        return res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation);
+    }
+
+    /**
+     * Gets whether to mask a built-in display cutout of a display which is determined by the
+     * given display unique id.
+     *
+     * Loads the default config {@link R.bool#config_maskMainBuiltInDisplayCutout} if
+     * {@link R.array#config_displayUniqueIdArray} is not set.
+     *
+     * @hide
+     */
+    public static boolean getMaskBuiltInDisplayCutout(Resources res, String displayUniqueId) {
+        final int index = getDisplayCutoutConfigIndex(res, displayUniqueId);
+        final TypedArray array = res.obtainTypedArray(R.array.config_maskBuiltInDisplayCutoutArray);
+        boolean maskCutout;
+        if (index >= 0 && index < array.length()) {
+            maskCutout = array.getBoolean(index, false);
+        } else {
+            maskCutout = res.getBoolean(R.bool.config_maskMainBuiltInDisplayCutout);
+        }
+        array.recycle();
+        return maskCutout;
+    }
+
+    /**
+     * Gets whether to fill a built-in display cutout of a display which is determined by the
+     * given display unique id.
+     *
+     * Loads the default config{@link R.bool#config_fillMainBuiltInDisplayCutout} if
+     * {@link R.array#config_displayUniqueIdArray} is not set.
+     *
+     * @hide
+     */
+    public static boolean getFillBuiltInDisplayCutout(Resources res, String displayUniqueId) {
+        final int index = getDisplayCutoutConfigIndex(res, displayUniqueId);
+        final TypedArray array = res.obtainTypedArray(R.array.config_fillBuiltInDisplayCutoutArray);
+        boolean fillCutout;
+        if (index >= 0 && index < array.length()) {
+            fillCutout = array.getBoolean(index, false);
+        } else {
+            fillCutout = res.getBoolean(R.bool.config_fillMainBuiltInDisplayCutout);
+        }
+        array.recycle();
+        return fillCutout;
+    }
+
+    /**
+     * Gets the waterfall cutout by the given display unique id.
+     *
+     * Loads the default waterfall dimens if {@link R.array#config_displayUniqueIdArray} is not set.
+     * {@link R.dimen#waterfall_display_left_edge_size},
+     * {@link R.dimen#waterfall_display_top_edge_size},
+     * {@link R.dimen#waterfall_display_right_edge_size},
+     * {@link R.dimen#waterfall_display_bottom_edge_size}
+     */
+    private static Insets getWaterfallInsets(Resources res, String displayUniqueId) {
+        Insets insets;
+        final int index = getDisplayCutoutConfigIndex(res, displayUniqueId);
+        final TypedArray array = res.obtainTypedArray(R.array.config_waterfallCutoutArray);
+        if (index >= 0 && index < array.length() && array.getResourceId(index, 0) > 0) {
+            final int resourceId = array.getResourceId(index, 0);
+            final TypedArray waterfall = res.obtainTypedArray(resourceId);
+            insets = Insets.of(
+                    waterfall.getDimensionPixelSize(0 /* waterfall left edge size */, 0),
+                    waterfall.getDimensionPixelSize(1 /* waterfall top edge size */, 0),
+                    waterfall.getDimensionPixelSize(2 /* waterfall right edge size */, 0),
+                    waterfall.getDimensionPixelSize(3 /* waterfall bottom edge size */, 0));
+            waterfall.recycle();
+        } else {
+            insets = loadWaterfallInset(res);
+        }
+        array.recycle();
+        return insets;
+    }
+
+    /**
      * Creates the display cutout according to
      * @android:string/config_mainBuiltInDisplayCutoutRectApproximation, which is the closest
      * rectangle-base approximation of the cutout.
      *
      * @hide
      */
-    public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth,
-            int displayHeight) {
-        return pathAndDisplayCutoutFromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
-                res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
+    public static DisplayCutout fromResourcesRectApproximation(Resources res,
+            String displayUniqueId, int displayWidth, int displayHeight) {
+        return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId),
+                getDisplayCutoutApproximationRect(res, displayUniqueId),
                 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
-                loadWaterfallInset(res)).second;
+                getWaterfallInsets(res, displayUniqueId)).second;
     }
 
     /**
@@ -892,11 +1022,11 @@
      *
      * @hide
      */
-    public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
-        return pathAndDisplayCutoutFromSpec(
-                res.getString(R.string.config_mainBuiltInDisplayCutout), null,
+    public static Path pathFromResources(Resources res, String displayUniqueId, int displayWidth,
+            int displayHeight) {
+        return pathAndDisplayCutoutFromSpec(getDisplayCutoutPath(res, displayUniqueId), null,
                 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT,
-                loadWaterfallInset(res)).first;
+                getWaterfallInsets(res, displayUniqueId)).first;
     }
 
     /**
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index f4539c2..9ce4122 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,6 +53,7 @@
 import android.view.KeyEvent;
 import android.view.InputEvent;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.InputChannel;
@@ -720,14 +721,15 @@
             int displayId, in IDisplayWindowInsetsController displayWindowInsetsController);
 
     /**
-     * Called when a remote process modifies insets on a display window container.
+     * Called when a remote process updates the requested visibilities of insets on a display window
+     * container.
      */
-    void modifyDisplayWindowInsets(int displayId, in InsetsState state);
+    void updateDisplayWindowRequestedVisibilities(int displayId, in InsetsVisibilities vis);
 
     /**
      * Called to get the expected window insets.
      *
-     * @return {@code true} if system bars are always comsumed.
+     * @return {@code true} if system bars are always consumed.
      */
     boolean getWindowInsets(in WindowManager.LayoutParams attrs, int displayId,
             out InsetsState outInsetsState);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7bad5cb..a6abed0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -32,6 +32,7 @@
 import android.view.WindowManager;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
@@ -46,12 +47,12 @@
  */
 interface IWindowSession {
     int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
-            in int viewVisibility, in int layerStackId, in InsetsState requestedVisibility,
+            in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
             out InputChannel outInputChannel, out InsetsState insetsState,
             out InsetsSourceControl[] activeControls);
     int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, in int userId,
-            in InsetsState requestedVisibility, out InputChannel outInputChannel,
+            in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
             out InsetsState insetsState, out InsetsSourceControl[] activeControls);
     int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, out InsetsState insetsState);
@@ -285,10 +286,9 @@
     oneway void updateTapExcludeRegion(IWindow window, in Region region);
 
     /**
-     * Called when the client has changed the local insets state, and now the server should reflect
-     * that new state.
+     * Updates the requested visibilities of insets.
      */
-    oneway void insetsModified(IWindow window, in InsetsState state);
+    oneway void updateRequestedVisibilities(IWindow window, in InsetsVisibilities visibilities);
 
     /**
      * Called when the system gesture exclusion has changed.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 6f915c9..acdaa52 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -107,9 +107,12 @@
                 boolean hasControl);
 
         /**
-         * Called when insets have been modified by the client and should be reported back to WM.
+         * Called when the requested visibilities of insets have been modified by the client.
+         * The visibilities should be reported back to WM.
+         *
+         * @param visibilities A collection of the requested visibilities.
          */
-        void onInsetsModified(InsetsState insetsState);
+        void updateRequestedVisibilities(InsetsVisibilities visibilities);
 
         /**
          * @return Whether the host has any callbacks it wants to synchronize the animations with.
@@ -536,10 +539,8 @@
     /** The state dispatched from server */
     private final InsetsState mLastDispatchedState = new InsetsState();
 
-    // TODO: Use other class to represent the requested visibility of each type, because the
-    //       display frame and the frame in each source are not used.
     /** The requested visibilities sent to server */
-    private final InsetsState mRequestedState = new InsetsState();
+    private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
 
     private final Rect mFrame = new Rect();
     private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator;
@@ -801,7 +802,7 @@
             }
         }
 
-        boolean requestedStateStale = false;
+        boolean requestedVisibilityStale = false;
         final int[] showTypes = new int[1];
         final int[] hideTypes = new int[1];
 
@@ -822,20 +823,20 @@
             final InsetsSourceConsumer consumer = getSourceConsumer(type);
             consumer.setControl(control, showTypes, hideTypes);
 
-            if (!requestedStateStale) {
+            if (!requestedVisibilityStale) {
                 final boolean requestedVisible = consumer.isRequestedVisible();
 
                 // We might have changed our requested visibilities while we don't have the control,
                 // so we need to update our requested state once we have control. Otherwise, our
                 // requested state at the server side might be incorrect.
                 final boolean requestedVisibilityChanged =
-                        requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type);
+                        requestedVisible != mRequestedVisibilities.getVisibility(type);
 
                 // The IME client visibility will be reset by insets source provider while updating
                 // control, so if IME is requested visible, we need to send the request to server.
                 final boolean imeRequestedVisible = type == ITYPE_IME && requestedVisible;
 
-                requestedStateStale = requestedVisibilityChanged || imeRequestedVisible;
+                requestedVisibilityStale = requestedVisibilityChanged || imeRequestedVisible;
             }
         }
 
@@ -861,7 +862,7 @@
         }
 
         // InsetsSourceConsumer#setControl might change the requested visibility.
-        updateRequestedVisibility();
+        updateRequestedVisibilities();
     }
 
     @Override
@@ -1015,7 +1016,7 @@
         if (types == 0) {
             // nothing to animate.
             listener.onCancelled(null);
-            updateRequestedVisibility();
+            updateRequestedVisibilities();
             if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
             return;
         }
@@ -1051,7 +1052,7 @@
                     }
                 });
             }
-            updateRequestedVisibility();
+            updateRequestedVisibilities();
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
             return;
         }
@@ -1059,7 +1060,7 @@
         if (typesReady == 0) {
             if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
             listener.onCancelled(null);
-            updateRequestedVisibility();
+            updateRequestedVisibilities();
             return;
         }
 
@@ -1091,7 +1092,7 @@
         } else {
             hideDirectly(types, false /* animationFinished */, animationType, fromIme);
         }
-        updateRequestedVisibility();
+        updateRequestedVisibilities();
     }
 
     /**
@@ -1348,7 +1349,7 @@
     /**
      * Sends the requested visibilities to window manager if any of them is changed.
      */
-    private void updateRequestedVisibility() {
+    private void updateRequestedVisibilities() {
         boolean changed = false;
         for (int i = mRequestedVisibilityChanged.size() - 1; i >= 0; i--) {
             final InsetsSourceConsumer consumer = mRequestedVisibilityChanged.valueAt(i);
@@ -1357,8 +1358,8 @@
                 continue;
             }
             final boolean requestedVisible = consumer.isRequestedVisible();
-            if (requestedVisible != mRequestedState.getSourceOrDefaultVisibility(type)) {
-                mRequestedState.getSource(type).setVisible(requestedVisible);
+            if (mRequestedVisibilities.getVisibility(type) != requestedVisible) {
+                mRequestedVisibilities.setVisibility(type, requestedVisible);
                 changed = true;
             }
         }
@@ -1366,11 +1367,11 @@
         if (!changed) {
             return;
         }
-        mHost.onInsetsModified(mRequestedState);
+        mHost.updateRequestedVisibilities(mRequestedVisibilities);
     }
 
-    InsetsState getRequestedVisibility() {
-        return mRequestedState;
+    InsetsVisibilities getRequestedVisibilities() {
+        return mRequestedVisibilities;
     }
 
     @VisibleForTesting
@@ -1425,7 +1426,7 @@
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
             getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
         }
-        updateRequestedVisibility();
+        updateRequestedVisibilities();
 
         if (fromIme) {
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
@@ -1441,7 +1442,7 @@
         for (int i = internalTypes.size() - 1; i >= 0; i--) {
             getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
         }
-        updateRequestedVisibility();
+        updateRequestedVisibilities();
 
         if (fromIme) {
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 37101b7..f4444a1 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -878,16 +878,5 @@
                 + ", mSources= { " + joiner
                 + " }";
     }
-
-    public @NonNull String toSourceVisibilityString() {
-        StringJoiner joiner = new StringJoiner(", ");
-        for (int i = 0; i < SIZE; i++) {
-            InsetsSource source = mSources[i];
-            if (source != null) {
-                joiner.add(typeToString(i) + ": " + (source.isVisible() ? "visible" : "invisible"));
-            }
-        }
-        return joiner.toString();
-    }
 }
 
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/core/java/android/view/InsetsVisibilities.aidl
new file mode 100644
index 0000000..bd573ea
--- /dev/null
+++ b/core/java/android/view/InsetsVisibilities.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.view;
+
+parcelable InsetsVisibilities;
diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java
new file mode 100644
index 0000000..30668ba
--- /dev/null
+++ b/core/java/android/view/InsetsVisibilities.java
@@ -0,0 +1,130 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.StringJoiner;
+
+/**
+ * A collection of visibilities of insets. This is used for carrying the requested visibilities.
+ * @hide
+ */
+public class InsetsVisibilities implements Parcelable {
+
+    private static final int UNSPECIFIED = 0;
+    private static final int VISIBLE = 1;
+    private static final int INVISIBLE = -1;
+
+    private final int[] mVisibilities = new int[InsetsState.SIZE];
+
+    public InsetsVisibilities() {
+    }
+
+    public InsetsVisibilities(InsetsVisibilities other) {
+        set(other);
+    }
+
+    public InsetsVisibilities(Parcel in) {
+        in.readIntArray(mVisibilities);
+    }
+
+    /**
+     * Copies from another {@link InsetsVisibilities}.
+     *
+     * @param other an instance of {@link InsetsVisibilities}.
+     */
+    public void set(InsetsVisibilities other) {
+        System.arraycopy(other.mVisibilities, InsetsState.FIRST_TYPE, mVisibilities,
+                InsetsState.FIRST_TYPE, InsetsState.SIZE);
+    }
+
+    /**
+     * Sets a visibility to a type.
+     *
+     * @param type The {@link @InsetsState.InternalInsetsType}.
+     * @param visible {@code true} represents visible; {@code false} represents invisible.
+     */
+    public void setVisibility(@InsetsState.InternalInsetsType int type, boolean visible) {
+        mVisibilities[type] = visible ? VISIBLE : INVISIBLE;
+    }
+
+    /**
+     * Returns the specified insets visibility of the type. If it has never been specified,
+     * this returns the default visibility.
+     *
+     * @param type The {@link @InsetsState.InternalInsetsType}.
+     * @return The specified visibility or the default one if it is not specified.
+     */
+    public boolean getVisibility(@InsetsState.InternalInsetsType int type) {
+        final int visibility = mVisibilities[type];
+        return visibility == UNSPECIFIED
+                ? InsetsState.getDefaultVisibility(type)
+                : visibility == VISIBLE;
+    }
+
+    @Override
+    public String toString() {
+        StringJoiner joiner = new StringJoiner(", ");
+        for (int type = InsetsState.FIRST_TYPE; type <= InsetsState.LAST_TYPE; type++) {
+            final int visibility = mVisibilities[type];
+            if (visibility != UNSPECIFIED) {
+                joiner.add(InsetsState.typeToString(type) + ": "
+                        + (visibility == VISIBLE ? "visible" : "invisible"));
+            }
+        }
+        return joiner.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(mVisibilities);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof InsetsVisibilities)) {
+            return false;
+        }
+        return Arrays.equals(mVisibilities, ((InsetsVisibilities) other).mVisibilities);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeIntArray(mVisibilities);
+    }
+
+    public static final @NonNull Creator<InsetsVisibilities> CREATOR =
+            new Creator<InsetsVisibilities>() {
+
+        public InsetsVisibilities createFromParcel(Parcel in) {
+            return new InsetsVisibilities(in);
+        }
+
+        public InsetsVisibilities[] newArray(int size) {
+            return new InsetsVisibilities[size];
+        }
+    };
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b300f30..d6186d7 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -148,6 +148,8 @@
             float width, float height, float vecX, float vecY,
             float maxStretchAmountX, float maxStretchAmountY, float childRelativeLeft,
             float childRelativeTop, float childRelativeRight, float childRelativeBottom);
+    private static native void nativeSetTrustedOverlay(long transactionObj, long nativeObject,
+            boolean isTrustedOverlay);
 
     private static native boolean nativeClearContentFrameStats(long nativeObject);
     private static native boolean nativeGetContentFrameStats(long nativeObject, WindowContentFrameStats outStats);
@@ -3435,6 +3437,17 @@
             return this;
         }
 
+        /**
+         * Sets the trusted overlay state on this SurfaceControl and it is inherited to all the
+         * children. The caller must hold the ACCESS_SURFACE_FLINGER permission.
+         * @hide
+         */
+        public Transaction setTrustedOverlay(SurfaceControl sc, boolean isTrustedOverlay) {
+            checkPreconditions(sc);
+            nativeSetTrustedOverlay(mNativeObject, sc.mNativeObject, isTrustedOverlay);
+            return this;
+        }
+
          /**
          * Merge the other transaction into this transaction, clearing the
          * other transaction as if it had been applied.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cb3eb06..27eb2a5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1133,7 +1133,7 @@
                     controlInsetsForCompatibility(mWindowAttributes);
                     res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                             getHostVisibility(), mDisplay.getDisplayId(), userId,
-                            mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
+                            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                             mTempControls);
                     if (mTranslator != null) {
                         mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
@@ -2517,6 +2517,14 @@
                 || lp.type == TYPE_VOLUME_OVERLAY;
     }
 
+    private Rect getWindowBoundsInsetSystemBars() {
+        final Rect bounds = new Rect(
+                mContext.getResources().getConfiguration().windowConfiguration.getBounds());
+        bounds.inset(mInsetsController.getState().calculateInsets(
+                bounds, Type.systemBars(), false /* ignoreVisibility */));
+        return bounds;
+    }
+
     int dipToPx(int dip) {
         final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
         return (int) (displayMetrics.density * dip + 0.5f);
@@ -2603,8 +2611,9 @@
                     || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                 // For wrap content, we have to remeasure later on anyways. Use size consistent with
                 // below so we get best use of the measure cache.
-                desiredWindowWidth = dipToPx(config.screenWidthDp);
-                desiredWindowHeight = dipToPx(config.screenHeightDp);
+                final Rect bounds = getWindowBoundsInsetSystemBars();
+                desiredWindowWidth = bounds.width();
+                desiredWindowHeight = bounds.height();
             } else {
                 // After addToDisplay, the frame contains the frameHint from window manager, which
                 // for most windows is going to be the same size as the result of relayoutWindow.
@@ -2681,9 +2690,9 @@
                         desiredWindowWidth = size.x;
                         desiredWindowHeight = size.y;
                     } else {
-                        Configuration config = res.getConfiguration();
-                        desiredWindowWidth = dipToPx(config.screenWidthDp);
-                        desiredWindowHeight = dipToPx(config.screenHeightDp);
+                        final Rect bounds = getWindowBoundsInsetSystemBars();
+                        desiredWindowWidth = bounds.width();
+                        desiredWindowHeight = bounds.height();
                     }
                 }
             }
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index ce882da..efffa2b 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -149,10 +149,10 @@
     }
 
     @Override
-    public void onInsetsModified(InsetsState insetsState) {
+    public void updateRequestedVisibilities(InsetsVisibilities vis) {
         try {
             if (mViewRoot.mAdded) {
-                mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, insetsState);
+                mViewRoot.mWindowSession.updateRequestedVisibilities(mViewRoot.mWindow, vis);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to call insetsModified", e);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fcfb0ab..8cc8866 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -400,6 +400,11 @@
      */
     int TRANSIT_PIP = 10;
     /**
+     * The screen is turning on.
+     * @hide
+     */
+    int TRANSIT_WAKE = 11;
+    /**
      * The first slot for custom transition types. Callers (like Shell) can make use of custom
      * transition types for dealing with special cases. These types are effectively ignored by
      * Core and will just be passed along as part of TransitionInfo objects. An example is
@@ -408,7 +413,7 @@
      * implementation.
      * @hide
      */
-    int TRANSIT_FIRST_CUSTOM = 11;
+    int TRANSIT_FIRST_CUSTOM = 12;
 
     /**
      * @hide
@@ -425,6 +430,7 @@
             TRANSIT_KEYGUARD_OCCLUDE,
             TRANSIT_KEYGUARD_UNOCCLUDE,
             TRANSIT_PIP,
+            TRANSIT_WAKE,
             TRANSIT_FIRST_CUSTOM
     })
     @Retention(RetentionPolicy.SOURCE)
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index ae54f51..c413a9b 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -135,7 +135,7 @@
      */
     @Override
     public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
-            int viewVisibility, int displayId, InsetsState requestedVisibility,
+            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
@@ -181,10 +181,10 @@
      */
     @Override
     public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
-            int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
+            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
-        return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibility,
+        return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
                 outInputChannel, outInsetsState, outActiveControls);
     }
 
@@ -454,7 +454,7 @@
     }
 
     @Override
-    public void insetsModified(android.view.IWindow window, android.view.InsetsState state) {
+    public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities)  {
     }
 
     @Override
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 566f154..8c64474 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
+import android.content.pm.ActivityInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.InsetsState;
@@ -78,6 +79,14 @@
     public ActivityManager.RunningTaskInfo taskInfo;
 
     /**
+     * The {@link ActivityInfo} of the target activity which to create the starting window.
+     * It can be null if the info is the same as the top in task info.
+     * @hide
+     */
+    @Nullable
+    public ActivityInfo targetActivityInfo;
+
+    /**
      * InsetsState of TopFullscreenOpaqueWindow
      * @hide
      */
@@ -174,6 +183,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeTypedObject(taskInfo, flags);
+        dest.writeTypedObject(targetActivityInfo, flags);
         dest.writeInt(startingWindowTypeParameter);
         dest.writeTypedObject(topOpaqueWindowInsetsState, flags);
         dest.writeTypedObject(topOpaqueWindowLayoutParams, flags);
@@ -185,6 +195,7 @@
 
     void readFromParcel(@NonNull Parcel source) {
         taskInfo = source.readTypedObject(ActivityManager.RunningTaskInfo.CREATOR);
+        targetActivityInfo = source.readTypedObject(ActivityInfo.CREATOR);
         startingWindowTypeParameter = source.readInt();
         topOpaqueWindowInsetsState = source.readTypedObject(InsetsState.CREATOR);
         topOpaqueWindowLayoutParams = source.readTypedObject(
@@ -198,6 +209,7 @@
     @Override
     public String toString() {
         return "StartingWindowInfo{taskId=" + taskInfo.taskId
+                + " targetActivityInfo=" + targetActivityInfo
                 + " displayId=" + taskInfo.displayId
                 + " topActivityType=" + taskInfo.topActivityType
                 + " preferredStartingWindowType="
diff --git a/core/java/android/window/TaskFragmentAppearedInfo.java b/core/java/android/window/TaskFragmentAppearedInfo.java
index 234b30c..89d9a95 100644
--- a/core/java/android/window/TaskFragmentAppearedInfo.java
+++ b/core/java/android/window/TaskFragmentAppearedInfo.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.SurfaceControl;
@@ -25,6 +26,7 @@
  * Data object for the TaskFragment info provided when a TaskFragment is presented to an organizer.
  * @hide
  */
+@TestApi
 public final class TaskFragmentAppearedInfo implements Parcelable {
 
     @NonNull
@@ -33,16 +35,19 @@
     @NonNull
     private final SurfaceControl mLeash;
 
+    /** @hide */
     public TaskFragmentAppearedInfo(
             @NonNull TaskFragmentInfo taskFragmentInfo, @NonNull SurfaceControl leash) {
         mTaskFragmentInfo = taskFragmentInfo;
         mLeash = leash;
     }
 
+    @NonNull
     public TaskFragmentInfo getTaskFragmentInfo() {
         return mTaskFragmentInfo;
     }
 
+    @NonNull
     public SurfaceControl getLeash() {
         return mLeash;
     }
@@ -52,6 +57,7 @@
         mLeash = in.readTypedObject(SurfaceControl.CREATOR);
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeTypedObject(mTaskFragmentInfo, flags);
@@ -79,6 +85,7 @@
                 + "}";
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index e4d6a6c..81ab783 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WindowingMode;
 
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -29,11 +30,12 @@
  * Data object for options to create TaskFragment with.
  * @hide
  */
+@TestApi
 public final class TaskFragmentCreationParams implements Parcelable {
 
     /** The organizer that will organize this TaskFragment. */
     @NonNull
-    private final ITaskFragmentOrganizer mOrganizer;
+    private final TaskFragmentOrganizerToken mOrganizer;
 
     /**
      * Unique token assigned from the client organizer to identify the {@link TaskFragmentInfo} when
@@ -58,25 +60,29 @@
     private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
 
     private TaskFragmentCreationParams(
-            @NonNull ITaskFragmentOrganizer organizer, @NonNull IBinder fragmentToken,
-            @NonNull IBinder ownerToken) {
+            @NonNull TaskFragmentOrganizerToken organizer,
+            @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
         mOrganizer = organizer;
         mFragmentToken = fragmentToken;
         mOwnerToken = ownerToken;
     }
 
-    public ITaskFragmentOrganizer getOrganizer() {
+    @NonNull
+    public TaskFragmentOrganizerToken getOrganizer() {
         return mOrganizer;
     }
 
+    @NonNull
     public IBinder getFragmentToken() {
         return mFragmentToken;
     }
 
+    @NonNull
     public IBinder getOwnerToken() {
         return mOwnerToken;
     }
 
+    @NonNull
     public Rect getInitialBounds() {
         return mInitialBounds;
     }
@@ -87,16 +93,17 @@
     }
 
     private TaskFragmentCreationParams(Parcel in) {
-        mOrganizer = ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
+        mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
         mOwnerToken = in.readStrongBinder();
         mInitialBounds.readFromParcel(in);
         mWindowingMode = in.readInt();
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeStrongInterface(mOrganizer);
+        mOrganizer.writeToParcel(dest, flags);
         dest.writeStrongBinder(mFragmentToken);
         dest.writeStrongBinder(mOwnerToken);
         mInitialBounds.writeToParcel(dest, flags);
@@ -128,16 +135,17 @@
                 + "}";
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
     }
 
     /** Builder to construct the options to create TaskFragment with. */
-    public static class Builder {
+    public static final class Builder {
 
         @NonNull
-        private final ITaskFragmentOrganizer mOrganizer;
+        private final TaskFragmentOrganizerToken mOrganizer;
 
         @NonNull
         private final IBinder mFragmentToken;
@@ -151,26 +159,29 @@
         @WindowingMode
         private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
 
-        public Builder(@NonNull ITaskFragmentOrganizer organizer, @NonNull IBinder fragmentToken,
-                @NonNull IBinder ownerToken) {
+        public Builder(@NonNull TaskFragmentOrganizerToken organizer,
+                @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
             mOrganizer = organizer;
             mFragmentToken = fragmentToken;
             mOwnerToken = ownerToken;
         }
 
         /** Sets the initial bounds for the TaskFragment. */
+        @NonNull
         public Builder setInitialBounds(@NonNull Rect bounds) {
             mInitialBounds.set(bounds);
             return this;
         }
 
         /** Sets the initial windowing mode for the TaskFragment. */
+        @NonNull
         public Builder setWindowingMode(@WindowingMode int windowingMode) {
             mWindowingMode = windowingMode;
             return this;
         }
 
         /** Constructs the options to create TaskFragment with. */
+        @NonNull
         public TaskFragmentCreationParams build() {
             final TaskFragmentCreationParams result = new TaskFragmentCreationParams(
                     mOrganizer, mFragmentToken, mOwnerToken);
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index fe25e57..dac420b 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -22,6 +22,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.os.IBinder;
@@ -35,6 +36,7 @@
  * Stores information about a particular TaskFragment.
  * @hide
  */
+@TestApi
 public final class TaskFragmentInfo implements Parcelable {
 
     /**
@@ -63,11 +65,13 @@
      * List of Activity tokens that are children of this TaskFragment. It only contains Activities
      * that belong to the organizer process for security.
      */
+    @NonNull
     private final List<IBinder> mActivities = new ArrayList<>();
 
     /** Relative position of the fragment's top left corner in the parent container. */
     private final Point mPositionInParent;
 
+    /** @hide */
     public TaskFragmentInfo(
             @NonNull IBinder fragmentToken, @NonNull WindowContainerToken token,
             @NonNull Configuration configuration, boolean isEmpty, boolean hasRunningActivity,
@@ -82,14 +86,17 @@
         mPositionInParent = requireNonNull(positionInParent);
     }
 
+    @NonNull
     public IBinder getFragmentToken() {
         return mFragmentToken;
     }
 
+    @NonNull
     public WindowContainerToken getToken() {
         return mToken;
     }
 
+    @NonNull
     public Configuration getConfiguration() {
         return mConfiguration;
     }
@@ -106,6 +113,7 @@
         return mIsVisible;
     }
 
+    @NonNull
     public List<IBinder> getActivities() {
         return mActivities;
     }
@@ -151,6 +159,7 @@
         mPositionInParent = requireNonNull(in.readTypedObject(Point.CREATOR));
     }
 
+    /** @hide */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeStrongBinder(mFragmentToken);
@@ -189,6 +198,7 @@
                 + "}";
     }
 
+    /** @hide */
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index b252df7..f22f0b2 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -26,9 +27,10 @@
 import java.util.concurrent.Executor;
 
 /**
- * Interface for WindowManager to delegate control of {@link com.android.server.wm.TaskFragment}.
+ * Interface for WindowManager to delegate control of {@code TaskFragment}.
  * @hide
  */
+@TestApi
 public class TaskFragmentOrganizer extends WindowOrganizer {
 
     /**
@@ -39,6 +41,7 @@
     /**
      * Creates a {@link Bundle} with an exception that can be passed to
      * {@link ITaskFragmentOrganizer#onTaskFragmentError}.
+     * @hide
      */
     public static Bundle putExceptionInBundle(@NonNull Throwable exception) {
         final Bundle exceptionBundle = new Bundle();
@@ -126,6 +129,8 @@
         super.applyTransaction(t);
     }
 
+    // Suppress the lint because it is not a registration method.
+    @SuppressWarnings("ExecutorRegistration")
     @Override
     public int applySyncTransaction(@NonNull WindowContainerTransaction t,
             @NonNull WindowContainerTransactionCallback callback) {
@@ -169,8 +174,11 @@
         }
     };
 
-    public ITaskFragmentOrganizer getIOrganizer() {
-        return mInterface;
+    private final TaskFragmentOrganizerToken mToken = new TaskFragmentOrganizerToken(mInterface);
+
+    @NonNull
+    public TaskFragmentOrganizerToken getOrganizerToken() {
+        return mToken;
     }
 
     private ITaskFragmentOrganizerController getController() {
diff --git a/core/java/android/window/TaskFragmentOrganizerToken.java b/core/java/android/window/TaskFragmentOrganizerToken.java
new file mode 100644
index 0000000..d5216a6
--- /dev/null
+++ b/core/java/android/window/TaskFragmentOrganizerToken.java
@@ -0,0 +1,97 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Interface to communicate between window manager and {@link TaskFragmentOrganizer}.
+ * <p>
+ * Window manager will dispatch TaskFragment information updates via this interface.
+ * It is necessary because {@link ITaskFragmentOrganizer} aidl interface can not be used as a
+ * {@link TestApi}.
+ * @hide
+ */
+@TestApi
+public final class TaskFragmentOrganizerToken implements Parcelable {
+    private final ITaskFragmentOrganizer mRealToken;
+
+    TaskFragmentOrganizerToken(ITaskFragmentOrganizer realToken) {
+        mRealToken = realToken;
+    }
+
+    /** @hide */
+    public IBinder asBinder() {
+        return mRealToken.asBinder();
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongInterface(mRealToken);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<TaskFragmentOrganizerToken> CREATOR =
+            new Creator<TaskFragmentOrganizerToken>() {
+                @Override
+                public TaskFragmentOrganizerToken createFromParcel(Parcel in) {
+                    final ITaskFragmentOrganizer realToken =
+                            ITaskFragmentOrganizer.Stub.asInterface(in.readStrongBinder());
+                    // The TaskFragmentOrganizerToken may be null for TaskOrganizer or
+                    // DisplayAreaOrganizer.
+                    if (realToken == null) {
+                        return null;
+                    }
+                    return new TaskFragmentOrganizerToken(realToken);
+                }
+
+                @Override
+                public TaskFragmentOrganizerToken[] newArray(int size) {
+                    return new TaskFragmentOrganizerToken[size];
+                }
+            };
+
+    @Override
+    public int hashCode() {
+        return mRealToken.asBinder().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "TaskFragmentOrganizerToken{" + mRealToken + "}";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (!(obj instanceof TaskFragmentOrganizerToken)) {
+            return false;
+        }
+        return mRealToken.asBinder() == ((TaskFragmentOrganizerToken) obj).asBinder();
+    }
+}
diff --git a/core/java/android/window/TransitionFilter.java b/core/java/android/window/TransitionFilter.java
index 607e316..bb3f90b 100644
--- a/core/java/android/window/TransitionFilter.java
+++ b/core/java/android/window/TransitionFilter.java
@@ -17,6 +17,7 @@
 package android.window;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.view.WindowManager.TransitionType;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -52,7 +53,7 @@
      * When non-null: this is a list of transition types that this filter applies to. This filter
      * will fail for transitions that aren't one of these types.
      */
-    @Nullable public int[] mTypeSet = null;
+    @Nullable public @TransitionType int[] mTypeSet = null;
 
     /**
      * A list of required changes. To pass, a transition must meet all requirements.
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index ebc66ef..a814399 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -29,6 +29,8 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.TransitionFlags;
+import static android.view.WindowManager.TransitionType;
 import static android.view.WindowManager.transitTypeToString;
 
 import android.annotation.IntDef;
@@ -42,7 +44,6 @@
 import android.os.Parcelable;
 import android.view.Surface;
 import android.view.SurfaceControl;
-import android.view.WindowManager;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -106,8 +107,8 @@
     })
     public @interface ChangeFlags {}
 
-    private final @WindowManager.TransitionOldType int mType;
-    private final @WindowManager.TransitionFlags int mFlags;
+    private final @TransitionType int mType;
+    private final @TransitionFlags int mFlags;
     private final ArrayList<Change> mChanges = new ArrayList<>();
 
     private SurfaceControl mRootLeash;
@@ -116,8 +117,7 @@
     private AnimationOptions mOptions;
 
     /** @hide */
-    public TransitionInfo(@WindowManager.TransitionOldType int type,
-            @WindowManager.TransitionFlags int flags) {
+    public TransitionInfo(@TransitionType int type, @TransitionFlags int flags) {
         mType = type;
         mFlags = flags;
     }
@@ -173,7 +173,7 @@
         mOptions = options;
     }
 
-    public int getType() {
+    public @TransitionType int getType() {
         return mType;
     }
 
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 380032b..a735bbc 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -410,7 +410,6 @@
     /**
      * Creates a new TaskFragment with the given options.
      * @param taskFragmentOptions the options used to create the TaskFragment.
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction createTaskFragment(
@@ -426,7 +425,6 @@
     /**
      * Deletes an existing TaskFragment. Any remaining activities below it will be destroyed.
      * @param taskFragment  the TaskFragment to be removed.
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction deleteTaskFragment(
@@ -447,7 +445,6 @@
      * @param activityIntent    intent to start the activity.
      * @param activityOptions    ActivityOptions to start the activity with.
      * @see android.content.Context#startActivity(Intent, Bundle).
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction startActivityInTaskFragment(
@@ -470,7 +467,6 @@
      * @param fragmentToken client assigned unique token to create TaskFragment with specified in
      *                      {@link TaskFragmentCreationParams#getFragmentToken()}.
      * @param activityToken activity to be reparented.
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction reparentActivityToTaskFragment(
@@ -490,7 +486,6 @@
      * @param oldParent children of this TaskFragment will be reparented.
      * @param newParent the new parent TaskFragment to move the children to. If {@code null}, the
      *                  children will be moved to the leaf Task.
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction reparentChildren(
@@ -535,7 +530,6 @@
      * @param errorCallbackToken    client provided token that will be passed back as parameter in
      *                              the callback if there is an error on the server side.
      * @see ITaskFragmentOrganizer#onTaskFragmentError
-     * @hide
      */
     @NonNull
     public WindowContainerTransaction setErrorCallbackToken(@NonNull IBinder errorCallbackToken) {
diff --git a/core/java/com/android/internal/app/AbstractResolverComparator.java b/core/java/com/android/internal/app/AbstractResolverComparator.java
index 40ada0b..c00d16c 100644
--- a/core/java/com/android/internal/app/AbstractResolverComparator.java
+++ b/core/java/com/android/internal/app/AbstractResolverComparator.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.BadParcelableException;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -109,17 +110,22 @@
 
     // get annotations of content from intent.
     private void getContentAnnotations(Intent intent) {
-        ArrayList<String> annotations = intent.getStringArrayListExtra(
-                Intent.EXTRA_CONTENT_ANNOTATIONS);
-        if (annotations != null) {
-            int size = annotations.size();
-            if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) {
-                size = NUM_OF_TOP_ANNOTATIONS_TO_USE;
+        try {
+            ArrayList<String> annotations = intent.getStringArrayListExtra(
+                    Intent.EXTRA_CONTENT_ANNOTATIONS);
+            if (annotations != null) {
+                int size = annotations.size();
+                if (size > NUM_OF_TOP_ANNOTATIONS_TO_USE) {
+                    size = NUM_OF_TOP_ANNOTATIONS_TO_USE;
+                }
+                mAnnotations = new String[size];
+                for (int i = 0; i < size; i++) {
+                    mAnnotations[i] = annotations.get(i);
+                }
             }
-            mAnnotations = new String[size];
-            for (int i = 0; i < size; i++) {
-                mAnnotations[i] = annotations.get(i);
-            }
+        } catch (BadParcelableException e) {
+            Log.i(TAG, "Couldn't unparcel intent annotations. Ignoring.");
+            mAnnotations = new String[0];
         }
     }
 
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index eeceafa..786af5f 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1269,7 +1269,8 @@
                             SELECTION_TYPE_NEARBY,
                             "",
                             -1);
-                    safelyStartActivity(ti);
+                    // Action bar is user-independent, always start as primary
+                    safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
                     finish();
                 }
         );
@@ -1290,7 +1291,8 @@
                             SELECTION_TYPE_EDIT,
                             "",
                             -1);
-                    safelyStartActivity(ti);
+                    // Action bar is user-independent, always start as primary
+                    safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
                     finish();
                 }
         );
@@ -3452,8 +3454,6 @@
 
         private View createProfileView(ViewGroup parent) {
             View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false);
-            profileRow.setBackground(
-                    getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
             mProfileView = profileRow.findViewById(R.id.profile_button);
             mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick);
             updateProfileViewButton();
@@ -3599,10 +3599,6 @@
             final ViewGroup viewGroup = (ViewGroup) holder.itemView;
             int start = getListPosition(position);
             int startType = getRowType(start);
-            if (viewGroup.getForeground() == null && position > 0) {
-                viewGroup.setForeground(
-                        getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
-            }
 
             int columnCount = holder.getColumnCount();
             int end = start + columnCount - 1;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 6f9da6f..d08f21c 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1257,13 +1257,32 @@
         // don't kill ourselves.
         StrictMode.disableDeathOnFileUriExposure();
         try {
-            safelyStartActivityInternal(cti);
+            UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
+            safelyStartActivityInternal(cti, currentUserHandle);
         } finally {
             StrictMode.enableDeathOnFileUriExposure();
         }
     }
 
-    private void safelyStartActivityInternal(TargetInfo cti) {
+    /**
+     * Start activity as a fixed user handle.
+     * @param cti TargetInfo to be launched.
+     * @param user User to launch this activity as.
+     */
+    @VisibleForTesting
+    public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+        // We're dispatching intents that might be coming from legacy apps, so
+        // don't kill ourselves.
+        StrictMode.disableDeathOnFileUriExposure();
+        try {
+            safelyStartActivityInternal(cti, user);
+        } finally {
+            StrictMode.enableDeathOnFileUriExposure();
+        }
+    }
+
+
+    private void safelyStartActivityInternal(TargetInfo cti, UserHandle user) {
         // If the target is suspended, the activity will not be successfully launched.
         // Do not unregister from package manager updates in this case
         if (!cti.isSuspended() && mRegistered) {
@@ -1280,18 +1299,17 @@
         if (mProfileSwitchMessageId != -1) {
             Toast.makeText(this, getString(mProfileSwitchMessageId), Toast.LENGTH_LONG).show();
         }
-        UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
         if (!mSafeForwardingMode) {
-            if (cti.startAsUser(this, null, currentUserHandle)) {
+            if (cti.startAsUser(this, null, user)) {
                 onActivityStarted(cti);
-                maybeLogCrossProfileTargetLaunch(cti, currentUserHandle);
+                maybeLogCrossProfileTargetLaunch(cti, user);
             }
             return;
         }
         try {
-            if (cti.startAsCaller(this, null, currentUserHandle.getIdentifier())) {
+            if (cti.startAsCaller(this, null, user.getIdentifier())) {
                 onActivityStarted(cti);
-                maybeLogCrossProfileTargetLaunch(cti, currentUserHandle);
+                maybeLogCrossProfileTargetLaunch(cti, user);
             }
         } catch (RuntimeException e) {
             Slog.wtf(TAG, "Unable to launch as uid " + mLaunchedFromUid
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index c9a9e51..55a2052 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -16,9 +16,11 @@
 
 package com.android.internal.display;
 
+import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
+import android.hardware.display.BrightnessInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.net.Uri;
@@ -61,10 +63,10 @@
                     updateBrightnessFloatFromInt(msg.arg1);
                     break;
                 case MSG_UPDATE_INT:
-                    updateBrightnessIntFromFloat(Float.intBitsToFloat(msg.arg1));
+                    updateBrightnessIntFromFloat((BrightnessInfo) msg.obj);
                     break;
                 case MSG_UPDATE_BOTH:
-                    updateBoth(Float.intBitsToFloat(msg.arg1));
+                    updateBoth((BrightnessInfo) msg.obj, Float.intBitsToFloat(msg.arg1));
                     break;
                 default:
                     super.handleMessage(msg);
@@ -95,11 +97,11 @@
         brightnessSyncObserver = new BrightnessSyncObserver();
         brightnessSyncObserver.startObserving();
 
-        final float currentFloatBrightness = getScreenBrightnessFloat();
+        final BrightnessInfo brightnessInfo = getBrightnessInfo();
         final int currentIntBrightness = getScreenBrightnessInt(mContext);
 
-        if (!Float.isNaN(currentFloatBrightness)) {
-            updateBrightnessIntFromFloat(currentFloatBrightness);
+        if (brightnessInfo != null && !Float.isNaN(brightnessInfo.brightness)) {
+            updateBrightnessIntFromFloat(brightnessInfo);
         } else if (currentIntBrightness != -1) {
             updateBrightnessFloatFromInt(currentIntBrightness);
         } else {
@@ -112,15 +114,23 @@
 
     /**
      * Converts between the int brightness system and the float brightness system.
+     *
+     * @param brightnessInt The int brightness value to convert.
      */
     public static float brightnessIntToFloat(int brightnessInt) {
+        return brightnessIntToFloat(brightnessInt, null);
+    }
+
+    private static float brightnessIntToFloat(int brightnessInt, BrightnessInfo info) {
         if (brightnessInt == PowerManager.BRIGHTNESS_OFF) {
             return PowerManager.BRIGHTNESS_OFF_FLOAT;
         } else if (brightnessInt == PowerManager.BRIGHTNESS_INVALID) {
             return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         } else {
-            final float minFloat = PowerManager.BRIGHTNESS_MIN;
-            final float maxFloat = PowerManager.BRIGHTNESS_MAX;
+            final float minFloat = info != null
+                    ? info.brightnessMinimum : PowerManager.BRIGHTNESS_MIN;
+            final float maxFloat = info != null
+                    ? info.brightnessMaximum : PowerManager.BRIGHTNESS_MAX;
             final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
             final float maxInt = PowerManager.BRIGHTNESS_ON;
             return MathUtils.constrainedMap(minFloat, maxFloat, minInt, maxInt, brightnessInt);
@@ -128,29 +138,28 @@
     }
 
     /**
-     * Converts between the float brightness system and the int brightness system.
-     */
-    public static int brightnessFloatToInt(float brightnessFloat) {
-        return Math.round(brightnessFloatToIntRange(brightnessFloat));
-    }
-
-    /**
      * Translates specified value from the float brightness system to the int brightness system,
      * given the min/max of each range. Accounts for special values such as OFF and invalid values.
      * Value returned as a float primitive (to preserve precision), but is a value within the
      * int-system range.
+     *
+     * @param brightnessFloat The float brightness value to convert.
+     * @param info Brightness information to use in the conversion.
      */
-    public static float brightnessFloatToIntRange(float brightnessFloat) {
+    public static int brightnessFloatToInt(float brightnessFloat, BrightnessInfo info) {
         if (floatEquals(brightnessFloat, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
             return PowerManager.BRIGHTNESS_OFF;
         } else if (Float.isNaN(brightnessFloat)) {
             return PowerManager.BRIGHTNESS_INVALID;
         } else {
-            final float minFloat = PowerManager.BRIGHTNESS_MIN;
-            final float maxFloat = PowerManager.BRIGHTNESS_MAX;
+            final float minFloat = info != null
+                    ? info.brightnessMinimum : PowerManager.BRIGHTNESS_MIN;
+            final float maxFloat = info != null
+                    ? info.brightnessMaximum : PowerManager.BRIGHTNESS_MAX;
             final float minInt = PowerManager.BRIGHTNESS_OFF + 1;
             final float maxInt = PowerManager.BRIGHTNESS_ON;
-            return MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat, brightnessFloat);
+            return Math.round(MathUtils.constrainedMap(minInt, maxInt, minFloat, maxFloat,
+                    brightnessFloat));
         }
     }
 
@@ -185,35 +194,37 @@
      * @param value Brightness value as int to store in the float setting.
      */
     private void updateBrightnessFloatFromInt(int value) {
-        if (brightnessFloatToInt(mPreferredSettingValue) == value) {
+        final BrightnessInfo info = getBrightnessInfo();
+        if (brightnessFloatToInt(mPreferredSettingValue, info) == value) {
             return;
         }
 
-        mPreferredSettingValue = brightnessIntToFloat(value);
+        mPreferredSettingValue = brightnessIntToFloat(value, info);
         final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue);
         mHandler.removeMessages(MSG_UPDATE_BOTH);
         mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget();
     }
 
     /**
-     * Updates the settings based on a passed in float value. This is called whenever the float
-     * setting changes. mPreferredSettingValue holds the most recently updated brightness value
-     * as a float that we would like the display to be set to.
+     * Updates the settings from the specified {@link BrightnessInfo}. This is called whenever the
+     * float brightness changed from DisplayManager. mPreferredSettingValue holds the most recently
+     * updated brightness value as a float that we would like the display to be set to.
      *
      * We then schedule an update to both the int and float settings, but, remove all the other
      * messages to update all, to prevent us getting stuck in a loop.
      *
-     * @param value Brightness setting as float to store in int setting.
+     * @param brightnessInfo Current brightness information
      */
-    private void updateBrightnessIntFromFloat(float value) {
+    private void updateBrightnessIntFromFloat(@NonNull BrightnessInfo brightnessInfo) {
+        final float value = brightnessInfo.brightness;
         if (floatEquals(mPreferredSettingValue, value)) {
             return;
         }
 
         mPreferredSettingValue = value;
-        final int newBrightnessAsIntBits = Float.floatToIntBits(mPreferredSettingValue);
         mHandler.removeMessages(MSG_UPDATE_BOTH);
-        mHandler.obtainMessage(MSG_UPDATE_BOTH, newBrightnessAsIntBits, 0).sendToTarget();
+        mHandler.obtainMessage(MSG_UPDATE_BOTH, Float.floatToIntBits(value), 0, brightnessInfo)
+                .sendToTarget();
     }
 
 
@@ -222,16 +233,24 @@
      * mDisplayManager.setBrightness automatically checks for changes
      * Settings.System.putIntForUser needs to be checked, to prevent an extra callback to this class
      *
+     * @param brightnessInfo Brightness information, takes precedent over newBrightnessFloat
      * @param newBrightnessFloat Brightness setting as float to store in both settings
      */
-    private void updateBoth(float newBrightnessFloat) {
-        int newBrightnessInt = brightnessFloatToInt(newBrightnessFloat);
+    private void updateBoth(BrightnessInfo brightnessInfo, float newBrightnessFloat) {
+        int newBrightnessInt = brightnessFloatToInt(newBrightnessFloat, brightnessInfo);
         mDisplayManager.setBrightness(Display.DEFAULT_DISPLAY, newBrightnessFloat);
         if (getScreenBrightnessInt(mContext) != newBrightnessInt) {
             Settings.System.putIntForUser(mContext.getContentResolver(),
                     Settings.System.SCREEN_BRIGHTNESS, newBrightnessInt, UserHandle.USER_CURRENT);
         }
+    }
 
+    private BrightnessInfo getBrightnessInfo() {
+        final Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        if (display != null) {
+            return display.getBrightnessInfo();
+        }
+        return null;
     }
 
     /**
@@ -263,10 +282,15 @@
 
             @Override
             public void onDisplayChanged(int displayId) {
-                float currentFloat = getScreenBrightnessFloat();
-                int toSend = Float.floatToIntBits(currentFloat);
-                mHandler.removeMessages(MSG_UPDATE_INT);
-                mHandler.obtainMessage(MSG_UPDATE_INT, toSend, 0).sendToTarget();
+                if (displayId != Display.DEFAULT_DISPLAY) {
+                    return;
+                }
+
+                final BrightnessInfo info = getBrightnessInfo();
+                if (info != null) {
+                    mHandler.removeMessages(MSG_UPDATE_INT);
+                    mHandler.obtainMessage(MSG_UPDATE_INT, info).sendToTarget();
+                }
             }
         };
 
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 6ce7cea..be91aac 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -220,8 +220,9 @@
     public CallSession callStarted(Binder binder, int code, int workSourceUid) {
         noteNativeThreadId();
 
+        boolean collectCpu = canCollect();
         // We always want to collect data for latency if it's enabled, regardless of device state.
-        if (!mCollectLatencyData && !canCollect()) {
+        if (!mCollectLatencyData && !collectCpu) {
             return null;
         }
 
@@ -233,7 +234,7 @@
         s.timeStarted = -1;
         s.recordedCall = shouldRecordDetailedData();
 
-        if (mRecordingAllTransactionsForUid || s.recordedCall) {
+        if (collectCpu && (mRecordingAllTransactionsForUid || s.recordedCall)) {
             s.cpuTimeStarted = getThreadTimeMicro();
             s.timeStarted = getElapsedRealtimeMicro();
         } else if (mCollectLatencyData) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 4f940db..84a7f2f 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -25,7 +25,7 @@
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.service.notification.StatusBarNotification;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.view.AppearanceRegion;
@@ -192,12 +192,12 @@
      *                         stacks.
      * @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
      * @param behavior the behavior of the focused window.
-     * @param requestedState the collection of the requested visibilities of system insets.
+     * @param requestedVisibilities the collection of the requested visibilities of system insets.
      * @param packageName the package name of the focused app.
      */
     void onSystemBarAttributesChanged(int displayId, int appearance,
             in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            int behavior, in InsetsState requestedVisibilities, String packageName);
+            int behavior, in InsetsVisibilities requestedVisibilities, String packageName);
 
     /**
      * Notifies System UI to show transient bars. The transient bars are system bars, e.g., status
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 2fd1691..4dcc82e 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,7 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import com.android.internal.view.AppearanceRegion;
 
@@ -40,14 +40,14 @@
     public final IBinder mImeToken;
     public final boolean mNavbarColorManagedByIme;
     public final int mBehavior;
-    public final InsetsState mRequestedState;
+    public final InsetsVisibilities mRequestedVisibilities;
     public final String mPackageName;
     public final int[] mTransientBarTypes;
 
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
             int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
             int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
-            boolean navbarColorManagedByIme, int behavior, InsetsState requestedState,
+            boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
             String packageName, @NonNull int[] transientBarTypes) {
         mIcons = new ArrayMap<>(icons);
         mDisabledFlags1 = disabledFlags1;
@@ -60,7 +60,7 @@
         mImeToken = imeToken;
         mNavbarColorManagedByIme = navbarColorManagedByIme;
         mBehavior = behavior;
-        mRequestedState = requestedState;
+        mRequestedVisibilities = requestedVisibilities;
         mPackageName = packageName;
         mTransientBarTypes = transientBarTypes;
     }
@@ -83,7 +83,7 @@
         dest.writeStrongBinder(mImeToken);
         dest.writeBoolean(mNavbarColorManagedByIme);
         dest.writeInt(mBehavior);
-        dest.writeTypedObject(mRequestedState, 0);
+        dest.writeTypedObject(mRequestedVisibilities, 0);
         dest.writeString(mPackageName);
         dest.writeIntArray(mTransientBarTypes);
     }
@@ -108,13 +108,14 @@
                     final IBinder imeToken = source.readStrongBinder();
                     final boolean navbarColorManagedByIme = source.readBoolean();
                     final int behavior = source.readInt();
-                    final InsetsState requestedState = source.readTypedObject(InsetsState.CREATOR);
+                    final InsetsVisibilities requestedVisibilities =
+                            source.readTypedObject(InsetsVisibilities.CREATOR);
                     final String packageName = source.readString();
                     final int[] transientBarTypes = source.createIntArray();
                     return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
                             appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
                             disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
-                            requestedState, packageName, transientBarTypes);
+                            requestedVisibilities, packageName, transientBarTypes);
                 }
 
                 @Override
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 1d07c26..98fc84c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -146,6 +146,7 @@
                 "android_os_MemoryFile.cpp",
                 "android_os_MessageQueue.cpp",
                 "android_os_Parcel.cpp",
+                "android_os_PerformanceHintManager.cpp",
                 "android_os_SELinux.cpp",
                 "android_os_ServiceManager.cpp",
                 "android_os_SharedMemory.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 406ccde..2fd1e54 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -143,6 +143,7 @@
 extern int register_android_os_ServiceManager(JNIEnv *env);
 extern int register_android_os_MessageQueue(JNIEnv* env);
 extern int register_android_os_Parcel(JNIEnv* env);
+extern int register_android_os_PerformanceHintManager(JNIEnv* env);
 extern int register_android_os_SELinux(JNIEnv* env);
 extern int register_android_os_VintfObject(JNIEnv *env);
 extern int register_android_os_VintfRuntimeInfo(JNIEnv *env);
@@ -1518,6 +1519,7 @@
         REG_JNI(register_android_os_SystemProperties),
         REG_JNI(register_android_os_Binder),
         REG_JNI(register_android_os_Parcel),
+        REG_JNI(register_android_os_PerformanceHintManager),
         REG_JNI(register_android_os_HidlMemory),
         REG_JNI(register_android_os_HidlSupport),
         REG_JNI(register_android_os_HwBinder),
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
new file mode 100644
index 0000000..d05a24f
--- /dev/null
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -0,0 +1,155 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "PerfHint-jni"
+
+#include "jni.h"
+
+#include <dlfcn.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <utils/Log.h>
+#include <vector>
+
+#include "core_jni_helpers.h"
+
+namespace android {
+
+namespace {
+
+struct APerformanceHintManager;
+struct APerformanceHintSession;
+
+typedef APerformanceHintManager* (*APH_getManager)();
+typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
+                                                      size_t, int64_t);
+typedef int64_t (*APH_getPreferredUpdateRateNanos)(APerformanceHintManager* manager);
+typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_closeSession)(APerformanceHintSession* session);
+
+bool gAPerformanceHintBindingInitialized = false;
+APH_getManager gAPH_getManagerFn = nullptr;
+APH_createSession gAPH_createSessionFn = nullptr;
+APH_getPreferredUpdateRateNanos gAPH_getPreferredUpdateRateNanosFn = nullptr;
+APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
+APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
+APH_closeSession gAPH_closeSessionFn = nullptr;
+
+void ensureAPerformanceHintBindingInitialized() {
+    if (gAPerformanceHintBindingInitialized) return;
+
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
+
+    gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
+    LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_getManager!");
+
+    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_createSession!");
+
+    gAPH_getPreferredUpdateRateNanosFn =
+            (APH_getPreferredUpdateRateNanos)dlsym(handle_,
+                                                   "APerformanceHint_getPreferredUpdateRateNanos");
+    LOG_ALWAYS_FATAL_IF(gAPH_getPreferredUpdateRateNanosFn == nullptr,
+                        "Failed to find required symbol "
+                        "APerformanceHint_getPreferredUpdateRateNanos!");
+
+    gAPH_updateTargetWorkDurationFn =
+            (APH_updateTargetWorkDuration)dlsym(handle_,
+                                                "APerformanceHint_updateTargetWorkDuration");
+    LOG_ALWAYS_FATAL_IF(gAPH_updateTargetWorkDurationFn == nullptr,
+                        "Failed to find required symbol "
+                        "APerformanceHint_updateTargetWorkDuration!");
+
+    gAPH_reportActualWorkDurationFn =
+            (APH_reportActualWorkDuration)dlsym(handle_,
+                                                "APerformanceHint_reportActualWorkDuration");
+    LOG_ALWAYS_FATAL_IF(gAPH_reportActualWorkDurationFn == nullptr,
+                        "Failed to find required symbol "
+                        "APerformanceHint_reportActualWorkDuration!");
+
+    gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_closeSession!");
+
+    gAPerformanceHintBindingInitialized = true;
+}
+
+} // namespace
+
+static jlong nativeAcquireManager(JNIEnv* env, jclass clazz) {
+    ensureAPerformanceHintBindingInitialized();
+    return reinterpret_cast<jlong>(gAPH_getManagerFn());
+}
+
+static jlong nativeGetPreferredUpdateRateNanos(JNIEnv* env, jclass clazz, jlong nativeManagerPtr) {
+    ensureAPerformanceHintBindingInitialized();
+    return gAPH_getPreferredUpdateRateNanosFn(
+            reinterpret_cast<APerformanceHintManager*>(nativeManagerPtr));
+}
+
+static jlong nativeCreateSession(JNIEnv* env, jclass clazz, jlong nativeManagerPtr, jintArray tids,
+                                 jlong initialTargetWorkDurationNanos) {
+    ensureAPerformanceHintBindingInitialized();
+    if (tids == nullptr) return 0;
+    std::vector<int32_t> tidsVector;
+    ScopedIntArrayRO tidsArray(env, tids);
+    for (size_t i = 0; i < tidsArray.size(); ++i) {
+        tidsVector.push_back(static_cast<int32_t>(tidsArray[i]));
+    }
+    return reinterpret_cast<jlong>(
+            gAPH_createSessionFn(reinterpret_cast<APerformanceHintManager*>(nativeManagerPtr),
+                                 tidsVector.data(), tidsVector.size(),
+                                 initialTargetWorkDurationNanos));
+}
+
+static void nativeUpdateTargetWorkDuration(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
+                                           jlong targetDurationNanos) {
+    ensureAPerformanceHintBindingInitialized();
+    gAPH_updateTargetWorkDurationFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+                                    targetDurationNanos);
+}
+
+static void nativeReportActualWorkDuration(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
+                                           jlong actualDurationNanos) {
+    ensureAPerformanceHintBindingInitialized();
+    gAPH_reportActualWorkDurationFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+                                    actualDurationNanos);
+}
+
+static void nativeCloseSession(JNIEnv* env, jclass clazz, jlong nativeSessionPtr) {
+    ensureAPerformanceHintBindingInitialized();
+    gAPH_closeSessionFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr));
+}
+
+static const JNINativeMethod gPerformanceHintMethods[] = {
+        {"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
+        {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
+        {"nativeCreateSession", "(J[IJ)J", (void*)nativeCreateSession},
+        {"nativeUpdateTargetWorkDuration", "(JJ)V", (void*)nativeUpdateTargetWorkDuration},
+        {"nativeReportActualWorkDuration", "(JJ)V", (void*)nativeReportActualWorkDuration},
+        {"nativeCloseSession", "(J)V", (void*)nativeCloseSession},
+};
+
+int register_android_os_PerformanceHintManager(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/os/PerformanceHintManager", gPerformanceHintMethods,
+                                NELEM(gPerformanceHintMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index dd80c73..50cd70f 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -825,6 +825,14 @@
     transaction->setShadowRadius(ctrl, shadowRadius);
 }
 
+static void nativeSetTrustedOverlay(JNIEnv* env, jclass clazz, jlong transactionObj,
+                                    jlong nativeObject, jboolean isTrustedOverlay) {
+    auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    transaction->setTrustedOverlay(ctrl, isTrustedOverlay);
+}
+
 static void nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject,
                                jfloat frameRate, jint compatibility, jint changeFrameRateStrategy) {
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -1999,6 +2007,8 @@
             (void*)nativeSetTransformHint },
     {"nativeGetTransformHint", "(J)I",
             (void*)nativeGetTransformHint },
+    {"nativeSetTrustedOverlay", "(JJZ)V",
+            (void*)nativeSetTrustedOverlay },
         // clang-format on
 };
 
diff --git a/core/res/res/drawable/chooser_row_layer_list.xml b/core/res/res/drawable/chooser_row_layer_list.xml
index 0800815..f5ba1e9 100644
--- a/core/res/res/drawable/chooser_row_layer_list.xml
+++ b/core/res/res/drawable/chooser_row_layer_list.xml
@@ -17,9 +17,11 @@
 */
 -->
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:bottom="-5dp" android:left="-5dp" android:right="-5dp">
+    <item>
         <shape android:shape="rectangle">
-            <stroke android:width="1dp" android:color="@color/chooser_row_divider"/>
+            <solid android:color="?android:attr/colorAccentSecondary"/>
+            <size android:width="128dp" android:height="2dp"/>
+            <corners android:radius="2dp" />
         </shape>
     </item>
 </layer-list>
diff --git a/core/res/res/layout/chooser_az_label_row.xml b/core/res/res/layout/chooser_az_label_row.xml
index 1b733fc..baf07ce 100644
--- a/core/res/res/layout/chooser_az_label_row.xml
+++ b/core/res/res/layout/chooser_az_label_row.xml
@@ -15,17 +15,12 @@
   ~ limitations under the License
   -->
 
-<!-- Separator applied as background -->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          android:text="@string/chooser_all_apps_button_label"
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
           android:contentDescription="@string/chooser_all_apps_button_label"
-          android:background="@drawable/chooser_row_layer_list"
-          android:textAppearance="?attr/textAppearanceSmall"
-          android:textColor="?attr/textColorSecondary"
-          android:textSize="14sp"
-          android:singleLine="true"
+          android:src="@drawable/chooser_row_layer_list"
           android:paddingTop="16dp"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
+          android:scaleType="center"
           android:gravity="center"/>
 
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 12f8cd4..6da4072 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1951,7 +1951,7 @@
     <string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string>
     <string name="call_notification_answer_action" msgid="5999246836247132937">"जवाफ दिनुहोस्"</string>
     <string name="call_notification_answer_video_action" msgid="2086030940195382249">"भिडियो"</string>
-    <string name="call_notification_decline_action" msgid="3700345945214000726">"अस्वीकार गर्नुहोस्"</string>
+    <string name="call_notification_decline_action" msgid="3700345945214000726">"काट्नुहोस्"</string>
     <string name="call_notification_hang_up_action" msgid="9130720590159188131">"फोन राख्नुहोस्"</string>
     <string name="call_notification_incoming_text" msgid="6143109825406638201">"आगमन कल"</string>
     <string name="call_notification_ongoing_text" msgid="3880832933933020875">"भइरहेको कल"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 89a7749..893aeda 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -87,7 +87,7 @@
     <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"ప్రాధాన్య నెట్‌వర్క్‌ను మార్చుకోవడానికి ప్రయత్నించండి. మార్చడానికి నొక్కండి."</string>
     <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"అత్యవసర కాలింగ్ అందుబాటులో లేదు"</string>
     <string name="EmergencyCallWarningSummary" msgid="1194185880092805497">"Wi-Fiతో అత్యవసర కాల్‌లు చేయలేరు"</string>
-    <string name="notification_channel_network_alert" msgid="4788053066033851841">"హెచ్చరికలు"</string>
+    <string name="notification_channel_network_alert" msgid="4788053066033851841">"అలర్ట్‌లు"</string>
     <string name="notification_channel_call_forward" msgid="8230490317314272406">"కాల్ ఫార్వార్డింగ్"</string>
     <string name="notification_channel_emergency_callback" msgid="54074839059123159">"అత్యవసర కాల్‌బ్యాక్ మోడ్"</string>
     <string name="notification_channel_mobile_data_status" msgid="1941911162076442474">"మొబైల్ డేటా స్థితి"</string>
@@ -287,7 +287,7 @@
     <string name="notification_channel_network_available" msgid="6083697929214165169">"నెట్‌వర్క్ అందుబాటులో ఉంది"</string>
     <string name="notification_channel_vpn" msgid="1628529026203808999">"VPN స్థితి"</string>
     <string name="notification_channel_device_admin" msgid="6384932669406095506">"మీ IT నిర్వాహకుల నుండి వచ్చే హెచ్చరికలు"</string>
-    <string name="notification_channel_alerts" msgid="5070241039583668427">"హెచ్చరికలు"</string>
+    <string name="notification_channel_alerts" msgid="5070241039583668427">"అలర్ట్‌లు"</string>
     <string name="notification_channel_retail_mode" msgid="3732239154256431213">"రిటైల్ డెమో"</string>
     <string name="notification_channel_usb" msgid="1528280969406244896">"USB కనెక్షన్"</string>
     <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"యాప్ అమలవుతోంది"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ef8e938..1586f16 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5047,4 +5047,80 @@
 
     <!-- the number of the max cached processes in the system. -->
     <integer name="config_customizedMaxCachedProcesses">32</integer>
+
+    <!-- The display cutout configs for secondary built-in display. -->
+    <string name="config_secondaryBuiltInDisplayCutout" translatable="false"></string>
+    <string name="config_secondaryBuiltInDisplayCutoutRectApproximation" translatable="false">
+        @string/config_secondaryBuiltInDisplayCutout
+    </string>
+    <bool name="config_fillSecondaryBuiltInDisplayCutout">false</bool>
+    <bool name="config_maskSecondaryBuiltInDisplayCutout">false</bool>
+
+    <!-- An array contains unique ids of all built-in displays and the unique id of a display can be
+         obtained from {@link Display#getUniqueId}. This array should be set for multi-display
+         devices if there are different display related configs(e.g. display cutout, rounded corner)
+         between each built-in display.
+         It is used as an index for multi-display related configs:
+         First look up the index of the unique id of the given built-in display unique id in this
+         array and use this index to get the info in corresponding config arrays such as:
+           - config_displayCutoutPathArray
+           - config_displayCutoutApproximationRectArray
+           - config_fillBuiltInDisplayCutoutArray
+           - config_maskBuiltInDisplayCutoutArray
+           - config_waterfallCutoutArray
+
+         Leave this array empty for single display device and the system will load the default main
+         built-in related configs.
+         -->
+    <string-array name="config_displayUniqueIdArray" translatable="false">
+        <!-- Example:
+        <item>"local:1234567891"</item> // main built-in display
+        <item>"local:1234567892"</item> // secondary built-in display
+        -->
+    </string-array>
+
+    <!-- The display cutout path config for each display in a multi-display device. -->
+    <string-array name="config_displayCutoutPathArray" translatable="false">
+        <item>@string/config_mainBuiltInDisplayCutout</item>
+        <item>@string/config_secondaryBuiltInDisplayCutout</item>
+    </string-array>
+
+    <!-- The display cutout approximation rect config for each display in a multi-display device.
+         -->
+    <string-array name="config_displayCutoutApproximationRectArray" translatable="false">
+        <item>@string/config_mainBuiltInDisplayCutoutRectApproximation</item>
+        <item>@string/config_secondaryBuiltInDisplayCutoutRectApproximation</item>
+    </string-array>
+
+    <!-- The maskBuiltInDisplayCutout config for each display in a multi-display device. -->
+    <array name="config_maskBuiltInDisplayCutoutArray" translatable="false">
+        <item>@bool/config_maskMainBuiltInDisplayCutout</item>
+        <item>@bool/config_maskSecondaryBuiltInDisplayCutout</item>
+    </array>
+
+    <!-- The fillBuiltInDisplayCutout config for each display in a multi-display device. -->
+    <array name="config_fillBuiltInDisplayCutoutArray" translatable="false">
+        <item>@bool/config_fillMainBuiltInDisplayCutout</item>
+        <item>@bool/config_fillSecondaryBuiltInDisplayCutout</item>
+    </array>
+
+    <array name="config_mainBuiltInDisplayWaterfallCutout" translatable="false">
+        <item>@dimen/waterfall_display_left_edge_size</item>
+        <item>@dimen/waterfall_display_top_edge_size</item>
+        <item>@dimen/waterfall_display_right_edge_size</item>
+        <item>@dimen/waterfall_display_bottom_edge_size</item>
+    </array>
+
+    <array name="config_secondaryBuiltInDisplayWaterfallCutout" translatable="false">
+        <item>@dimen/secondary_waterfall_display_left_edge_size</item>
+        <item>@dimen/secondary_waterfall_display_top_edge_size</item>
+        <item>@dimen/secondary_waterfall_display_right_edge_size</item>
+        <item>@dimen/secondary_waterfall_display_bottom_edge_size</item>
+    </array>
+
+    <!-- The waterfall cutout config for each display in a multi-display device. -->
+    <array name="config_waterfallCutoutArray" translatable="false">
+        <item>@array/config_mainBuiltInDisplayWaterfallCutout</item>
+        <item>@array/config_secondaryBuiltInDisplayWaterfallCutout</item>
+    </array>
 </resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index de7a117..7be9c7b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -920,7 +920,7 @@
 
     <dimen name="chooser_action_button_icon_size">18dp</dimen>
 
-    <!-- For Waterfall Display -->
+    <!-- For main built-in Waterfall Display -->
     <dimen name="waterfall_display_left_edge_size">0px</dimen>
     <dimen name="waterfall_display_top_edge_size">0px</dimen>
     <dimen name="waterfall_display_right_edge_size">0px</dimen>
@@ -943,4 +943,10 @@
     <dimen name="starting_surface_icon_size">160dp</dimen>
     <!-- The default width/height of the icon on the spec of adaptive icon drawable. -->
     <dimen name="starting_surface_default_icon_size">108dp</dimen>
+
+    <!-- For secondary built-in Waterfall Display -->
+    <dimen name="secondary_waterfall_display_left_edge_size">0px</dimen>
+    <dimen name="secondary_waterfall_display_top_edge_size">0px</dimen>
+    <dimen name="secondary_waterfall_display_right_edge_size">0px</dimen>
+    <dimen name="secondary_waterfall_display_bottom_edge_size">0px</dimen>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 908721f..cd590cbb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4426,4 +4426,21 @@
   <java-symbol type="bool" name="config_volumeShowRemoteSessions" />
 
   <java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
+
+  <java-symbol type="string" name="config_secondaryBuiltInDisplayCutout" />
+  <java-symbol type="string" name="config_secondaryBuiltInDisplayCutoutRectApproximation" />
+  <java-symbol type="bool" name="config_fillSecondaryBuiltInDisplayCutout" />
+  <java-symbol type="bool" name="config_maskSecondaryBuiltInDisplayCutout" />
+  <java-symbol type="array" name="config_displayUniqueIdArray" />
+  <java-symbol type="array" name="config_displayCutoutPathArray" />
+  <java-symbol type="array" name="config_displayCutoutApproximationRectArray" />
+  <java-symbol type="array" name="config_fillBuiltInDisplayCutoutArray" />
+  <java-symbol type="array" name="config_maskBuiltInDisplayCutoutArray" />
+  <java-symbol type="dimen" name="secondary_waterfall_display_left_edge_size" />
+  <java-symbol type="dimen" name="secondary_waterfall_display_top_edge_size" />
+  <java-symbol type="dimen" name="secondary_waterfall_display_right_edge_size" />
+  <java-symbol type="dimen" name="secondary_waterfall_display_bottom_edge_size" />
+  <java-symbol type="array" name="config_mainBuiltInDisplayWaterfallCutout" />
+  <java-symbol type="array" name="config_secondaryBuiltInDisplayWaterfallCutout" />
+  <java-symbol type="array" name="config_waterfallCutoutArray" />
 </resources>
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index d51004c..07e4333 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
@@ -16,6 +16,8 @@
 
 package android.app.appsearch;
 
+import static android.app.appsearch.SearchSpec.TERM_MATCH_PREFIX;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.expectThrows;
@@ -30,6 +32,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -100,4 +104,127 @@
                 .isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
         assertThat(appSearchException.getMessage()).startsWith("NullPointerException");
     }
+
+    @Test
+    public void testGetEmptyNextPage() throws Exception {
+        // Set the schema.
+        CompletableFuture<AppSearchResult<SetSchemaResponse>> schemaFuture =
+                new CompletableFuture<>();
+        mSearchSession.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(new AppSearchSchema.Builder("schema1").build())
+                        .setForceOverride(true).build(),
+                mExecutor, mExecutor, schemaFuture::complete);
+        schemaFuture.get().getResultValue();
+
+        // Create a document and index it.
+        GenericDocument document1 = new GenericDocument.Builder<>("namespace", "id1",
+                "schema1").build();
+        CompletableFuture<AppSearchBatchResult<String, Void>> putDocumentsFuture =
+                new CompletableFuture<>();
+        mSearchSession.put(
+                new PutDocumentsRequest.Builder().addGenericDocuments(document1).build(),
+                mExecutor, new BatchResultCallback<String, Void>() {
+                    @Override
+                    public void onResult(AppSearchBatchResult<String, Void> result) {
+                        putDocumentsFuture.complete(result);
+                    }
+
+                    @Override
+                    public void onSystemError(Throwable throwable) {
+                        putDocumentsFuture.completeExceptionally(throwable);
+                    }
+                });
+        putDocumentsFuture.get();
+
+        // Search and get the first page.
+        SearchSpec searchSpec = new SearchSpec.Builder()
+                .setTermMatch(TERM_MATCH_PREFIX)
+                .setResultCountPerPage(1)
+                .build();
+        SearchResults searchResults = mSearchSession.search("", searchSpec);
+
+        CompletableFuture<AppSearchResult<List<SearchResult>>> getNextPageFuture =
+                new CompletableFuture<>();
+        searchResults.getNextPage(mExecutor, getNextPageFuture::complete);
+        List<SearchResult> results = getNextPageFuture.get().getResultValue();
+        assertThat(results).hasSize(1);
+        assertThat(results.get(0).getGenericDocument()).isEqualTo(document1);
+
+        // We get all documents, and it shouldn't fail if we keep calling getNextPage().
+        getNextPageFuture = new CompletableFuture<>();
+        searchResults.getNextPage(mExecutor, getNextPageFuture::complete);
+        results = getNextPageFuture.get().getResultValue();
+        assertThat(results).isEmpty();
+    }
+
+    @Test
+    public void testGetEmptyNextPage_multiPages() throws Exception {
+        // Set the schema.
+        CompletableFuture<AppSearchResult<SetSchemaResponse>> schemaFuture =
+                new CompletableFuture<>();
+        mSearchSession.setSchema(
+                new SetSchemaRequest.Builder()
+                        .addSchemas(new AppSearchSchema.Builder("schema1").build())
+                        .setForceOverride(true).build(),
+                mExecutor, mExecutor, schemaFuture::complete);
+        schemaFuture.get().getResultValue();
+
+        // Create a document and insert 3 package1 documents
+        GenericDocument document1 = new GenericDocument.Builder<>("namespace", "id1",
+                "schema1").build();
+        GenericDocument document2 = new GenericDocument.Builder<>("namespace", "id2",
+                "schema1").build();
+        GenericDocument document3 = new GenericDocument.Builder<>("namespace", "id3",
+                "schema1").build();
+        CompletableFuture<AppSearchBatchResult<String, Void>> putDocumentsFuture =
+                new CompletableFuture<>();
+        mSearchSession.put(
+                new PutDocumentsRequest.Builder()
+                        .addGenericDocuments(document1, document2, document3).build(),
+                mExecutor, new BatchResultCallback<String, Void>() {
+                    @Override
+                    public void onResult(AppSearchBatchResult<String, Void> result) {
+                        putDocumentsFuture.complete(result);
+                    }
+
+                    @Override
+                    public void onSystemError(Throwable throwable) {
+                        putDocumentsFuture.completeExceptionally(throwable);
+                    }
+                });
+        putDocumentsFuture.get();
+
+        // Search for only 2 result per page
+        SearchSpec searchSpec = new SearchSpec.Builder()
+                .setTermMatch(TERM_MATCH_PREFIX)
+                .setResultCountPerPage(2)
+                .build();
+        SearchResults searchResults = mSearchSession.search("", searchSpec);
+
+        // Get the first page, it contains 2 results.
+        List<GenericDocument> outDocs = new ArrayList<>();
+        CompletableFuture<AppSearchResult<List<SearchResult>>> getNextPageFuture =
+                new CompletableFuture<>();
+        searchResults.getNextPage(mExecutor, getNextPageFuture::complete);
+        List<SearchResult> results = getNextPageFuture.get().getResultValue();
+        assertThat(results).hasSize(2);
+        outDocs.add(results.get(0).getGenericDocument());
+        outDocs.add(results.get(1).getGenericDocument());
+
+        // Get the second page, it contains only 1 result.
+        getNextPageFuture = new CompletableFuture<>();
+        searchResults.getNextPage(mExecutor, getNextPageFuture::complete);
+        results = getNextPageFuture.get().getResultValue();
+        assertThat(results).hasSize(1);
+        outDocs.add(results.get(0).getGenericDocument());
+
+        assertThat(outDocs).containsExactly(document1, document2, document3);
+
+        // We get all documents, and it shouldn't fail if we keep calling getNextPage().
+        getNextPageFuture = new CompletableFuture<>();
+        searchResults.getNextPage(mExecutor, getNextPageFuture::complete);
+        results = getNextPageFuture.get().getResultValue();
+        assertThat(results).isEmpty();
+    }
 }
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 7dea82d..69eb13f 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -22,12 +22,6 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeNotNull;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
 
 import android.os.PerformanceHintManager.Session;
 
@@ -120,92 +114,9 @@
     }
 
     @Test
-    public void testRateLimitWithDurationFastEnough() throws Exception {
-        FakeClock fakeClock = new FakeClock();
-        Session s = new Session(mIHintSessionMock, fakeClock, RATE_1000, TARGET_166);
-
-        reset(mIHintSessionMock);
-        fakeClock.setNow(0);
-        s.updateTargetWorkDuration(TARGET_166);
-
-        s.reportActualWorkDuration(TARGET_166 - 1);
-        s.reportActualWorkDuration(TARGET_166);
-        // we should not see update as the rate should be 10X for over-perform case.
-        verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
-        fakeClock.incrementClock(10 * RATE_1000);
-        s.reportActualWorkDuration(TARGET_166);
-        verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
-        fakeClock.incrementClock(1);
-        s.reportActualWorkDuration(TARGET_166);
-        // we should see update after rate limit
-        verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
-                eq(new long[] {TARGET_166 - 1, TARGET_166, TARGET_166, TARGET_166}),
-                eq(new long[] {0, 0, 10 * RATE_1000, 10 * RATE_1000 + 1}));
-
-        reset(mIHintSessionMock);
-        s.reportActualWorkDuration(TARGET_166);
-        s.reportActualWorkDuration(TARGET_166 - 1);
-        s.reportActualWorkDuration(TARGET_166 - 2);
-        // we should not see update as the rate should be 10X for over-perform case.
-        verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
-        fakeClock.incrementClock(10 * RATE_1000 + 1);
-        s.reportActualWorkDuration(TARGET_166);
-        s.reportActualWorkDuration(TARGET_166 - 1);
-        // we should see update now
-        verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
-                eq(new long[] {TARGET_166, TARGET_166 - 1, TARGET_166 - 2, TARGET_166}),
-                eq(new long[] {10 * RATE_1000 + 1, 10 * RATE_1000 + 1, 10 * RATE_1000 + 1,
-                    (10 * RATE_1000 + 1) * 2}));
-    }
-
-    @Test
-    public void testRateLimitWithDurationTooSlow() throws Exception {
-        FakeClock fakeClock = new FakeClock();
-        Session s = new Session(mIHintSessionMock, fakeClock, RATE_1000, TARGET_166);
-
-        reset(mIHintSessionMock);
-        fakeClock.setNow(0);
-        s.updateTargetWorkDuration(TARGET_166);
-
-        verify(mIHintSessionMock, times(1)).updateTargetWorkDuration(eq(TARGET_166));
-        // shouldn't update before rate limit
-        s.reportActualWorkDuration(TARGET_166 + 1);
-        verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
-
-        // shouldn't update when the time is exactly at rate limit
-        fakeClock.incrementClock(RATE_1000);
-        s.reportActualWorkDuration(TARGET_166 + 1);
-        verify(mIHintSessionMock, never()).reportActualWorkDuration(any(), any());
-
-        // should be ready for sending hint
-        fakeClock.incrementClock(1);
-        s.reportActualWorkDuration(TARGET_166 + 1);
-        verify(mIHintSessionMock, times(1)).reportActualWorkDuration(
-                eq(new long[] {TARGET_166 + 1, TARGET_166 + 1, TARGET_166 + 1}),
-                eq(new long[] {0 , RATE_1000, RATE_1000 + 1}));
-    }
-
-    @Test
     public void testCloseHintSession() {
         Session s = createSession();
         assumeNotNull(s);
         s.close();
     }
-
-    private static class FakeClock implements PerformanceHintManager.NanoClock {
-        private long mCurrentTime = 0L;
-
-        @Override
-        public long nanos() {
-            return mCurrentTime;
-        }
-
-        public void setNow(long nanos) {
-            mCurrentTime = nanos;
-        }
-
-        public void incrementClock(long nanos) {
-            mCurrentTime += nanos;
-        }
-    }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 6301f32..507638e 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -698,15 +698,15 @@
     @Test
     public void testRequestedState() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            final InsetsState state = mTestHost.getRequestedState();
+            final InsetsVisibilities request = mTestHost.getRequestedVisibilities();
 
             mController.hide(statusBars() | navigationBars());
-            assertFalse(state.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR));
-            assertFalse(state.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+            assertFalse(request.getVisibility(ITYPE_STATUS_BAR));
+            assertFalse(request.getVisibility(ITYPE_NAVIGATION_BAR));
 
             mController.show(statusBars() | navigationBars());
-            assertTrue(state.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR));
-            assertTrue(state.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+            assertTrue(request.getVisibility(ITYPE_STATUS_BAR));
+            assertTrue(request.getVisibility(ITYPE_NAVIGATION_BAR));
         });
     }
 
@@ -837,20 +837,20 @@
 
     public static class TestHost extends ViewRootInsetsControllerHost {
 
-        private final InsetsState mRequestedState = new InsetsState();
+        private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
 
         TestHost(ViewRootImpl viewRoot) {
             super(viewRoot);
         }
 
         @Override
-        public void onInsetsModified(InsetsState insetsState) {
-            mRequestedState.set(insetsState, true);
-            super.onInsetsModified(insetsState);
+        public void updateRequestedVisibilities(InsetsVisibilities visibilities) {
+            mRequestedVisibilities.set(visibilities);
+            super.updateRequestedVisibilities(visibilities);
         }
 
-        public InsetsState getRequestedState() {
-            return mRequestedState;
+        public InsetsVisibilities getRequestedVisibilities() {
+            return mRequestedVisibilities;
         }
     }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
new file mode 100644
index 0000000..5664e0b
--- /dev/null
+++ b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.view;
+
+import static android.view.InsetsState.FIRST_TYPE;
+import static android.view.InsetsState.LAST_TYPE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+import android.view.InsetsState.InternalInsetsType;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link InsetsVisibilities}.
+ *
+ * <p>Build/Install/Run:
+ *  atest FrameworksCoreTests:InsetsVisibilities
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InsetsVisibilitiesTest {
+
+    @Test
+    public void testEquals() {
+        final InsetsVisibilities v1 = new InsetsVisibilities();
+        final InsetsVisibilities v2 = new InsetsVisibilities();
+        final InsetsVisibilities v3 = new InsetsVisibilities();
+        assertEquals(v1, v2);
+        assertEquals(v1, v3);
+
+        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            v1.setVisibility(type, false);
+            v2.setVisibility(type, false);
+        }
+        assertEquals(v1, v2);
+        assertNotEquals(v1, v3);
+
+        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            v1.setVisibility(type, true);
+            v2.setVisibility(type, true);
+        }
+        assertEquals(v1, v2);
+        assertNotEquals(v1, v3);
+    }
+
+    @Test
+    public void testSet() {
+        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            final InsetsVisibilities v1 = new InsetsVisibilities();
+            final InsetsVisibilities v2 = new InsetsVisibilities();
+
+            v1.setVisibility(type, true);
+            assertNotEquals(v1, v2);
+
+            v2.set(v1);
+            assertEquals(v1, v2);
+
+            v2.setVisibility(type, false);
+            assertNotEquals(v1, v2);
+
+            v1.set(v2);
+            assertEquals(v1, v2);
+        }
+    }
+
+    @Test
+    public void testCopyConstructor() {
+        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            final InsetsVisibilities v1 = new InsetsVisibilities();
+            v1.setVisibility(type, true);
+            final InsetsVisibilities v2 = new InsetsVisibilities(v1);
+            assertEquals(v1, v2);
+
+            v2.setVisibility(type, false);
+            assertNotEquals(v1, v2);
+        }
+    }
+
+    @Test
+    public void testGetterAndSetter() {
+        final InsetsVisibilities v1 = new InsetsVisibilities();
+        final InsetsVisibilities v2 = new InsetsVisibilities();
+
+        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            assertEquals(InsetsState.getDefaultVisibility(type), v1.getVisibility(type));
+        }
+
+        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+            v1.setVisibility(type, true);
+            assertTrue(v1.getVisibility(type));
+
+            v2.setVisibility(type, false);
+            assertFalse(v2.getVisibility(type));
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index 7d4412c..0f05be0 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -24,7 +24,7 @@
 import android.os.Parcel;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -60,7 +60,7 @@
                 new Binder() /* imeToken */,
                 true /* navbarColorManagedByIme */,
                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                new InsetsState() /* requestedState */,
+                new InsetsVisibilities() /* requestedVisibilities */,
                 "test" /* packageName */,
                 new int[0] /* transientBarTypes */);
 
@@ -81,7 +81,7 @@
         assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
         assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
         assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
-        assertThat(copy.mRequestedState).isEqualTo(original.mRequestedState);
+        assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
         assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
         assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
     }
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index fe04f0d..c3b1cd74 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -28,7 +28,6 @@
 import android.hardware.display.DisplayManager;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.PerformanceHintManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
@@ -856,36 +855,6 @@
         callback.onPictureCaptured(picture);
     }
 
-    /** called by native */
-    static PerformanceHintManager.Session createHintSession(int[] tids) {
-        PerformanceHintManager performanceHintManager =
-                ProcessInitializer.sInstance.getHintManager();
-        if (performanceHintManager == null) {
-            return null;
-        }
-        // Native code will always set a target duration before reporting actual durations.
-        // So this is just a placeholder value that's never used.
-        long targetDurationNanos = 16666667;
-        return performanceHintManager.createHintSession(tids, targetDurationNanos);
-    }
-
-    /** called by native */
-    static void updateTargetWorkDuration(PerformanceHintManager.Session session,
-            long targetDurationNanos) {
-        session.updateTargetWorkDuration(targetDurationNanos);
-    }
-
-    /** called by native */
-    static void reportActualWorkDuration(PerformanceHintManager.Session session,
-            long actualDurationNanos) {
-        session.reportActualWorkDuration(actualDurationNanos);
-    }
-
-    /** called by native */
-    static void closeHintSession(PerformanceHintManager.Session session) {
-        session.close();
-    }
-
    /**
      * Interface used to receive callbacks when Webview requests a surface control.
      *
@@ -1152,7 +1121,6 @@
         private boolean mIsolated = false;
         private Context mContext;
         private String mPackageName;
-        private PerformanceHintManager mPerformanceHintManager;
         private IGraphicsStats mGraphicsStatsService;
         private IGraphicsStatsCallback mGraphicsStatsCallback = new IGraphicsStatsCallback.Stub() {
             @Override
@@ -1164,10 +1132,6 @@
         private ProcessInitializer() {
         }
 
-        synchronized PerformanceHintManager getHintManager() {
-            return mPerformanceHintManager;
-        }
-
         synchronized void setPackageName(String name) {
             if (mInitialized) return;
             mPackageName = name;
@@ -1218,10 +1182,6 @@
 
             initDisplayInfo();
 
-            // HintManager and HintSession are designed to be accessible from isoalted processes
-            // so not checking for isolated process here.
-            initHintSession();
-
             nSetIsHighEndGfx(ActivityManager.isHighEndGfx());
             // Defensively clear out the context in case we were passed a context that can leak
             // if we live longer than it, e.g. an activity context.
@@ -1265,11 +1225,6 @@
             mDisplayInitialized = true;
         }
 
-        private void initHintSession() {
-            if (mContext == null) return;
-            mPerformanceHintManager = mContext.getSystemService(PerformanceHintManager.class);
-        }
-
         private void rotateBuffer() {
             nRotateProcessStatsBuffer();
             requestBuffer();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
index d84554b..27bd53d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/JetpackTaskFragmentOrganizer.java
@@ -187,7 +187,7 @@
         }
 
         return new TaskFragmentCreationParams.Builder(
-                getIOrganizer(),
+                getOrganizerToken(),
                 fragmentToken,
                 ownerToken)
                 .setInitialBounds(bounds)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
index 7298d34..407c43d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitController.java
@@ -26,7 +26,9 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.window.TaskFragmentAppearedInfo;
 import android.window.TaskFragmentInfo;
 import android.window.WindowContainerTransaction;
@@ -40,6 +42,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Main controller class that manages split states and presentation.
@@ -57,8 +60,7 @@
     private SplitOrganizerCallback mSplitOrganizerCallback;
 
     public SplitController() {
-        mPresenter = new SplitPresenter(ActivityThread.currentActivityThread().getExecutor(),
-                this);
+        mPresenter = new SplitPresenter(new MainThreadExecutor(), this);
         // Register a callback to be notified about activities being created.
         ActivityThread.currentActivityThread().getApplication().registerActivityLifecycleCallbacks(
                 new LifecycleCallbacks());
@@ -99,39 +101,38 @@
 
     @Override
     public void onTaskFragmentAppeared(@NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {
-        for (TaskFragmentContainer container : mContainers) {
-            if (container.getTaskFragmentToken().equals(
-                    taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken())) {
-                container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo());
-                return;
-            }
+        TaskFragmentContainer container = getContainer(
+                taskFragmentAppearedInfo.getTaskFragmentInfo().getFragmentToken());
+        if (container == null) {
+            return;
         }
+
+        container.setInfo(taskFragmentAppearedInfo.getTaskFragmentInfo());
     }
 
     @Override
     public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
-        for (TaskFragmentContainer container : mContainers) {
-            if (container.getTaskFragmentToken().equals(taskFragmentInfo.getFragmentToken())) {
-                container.setInfo(taskFragmentInfo);
+        TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
+        if (container == null) {
+            return;
+        }
 
-                if (taskFragmentInfo.isEmpty()) {
-                    cleanupContainer(container, true /* shouldFinishDependent */);
-                    updateCallbackIfNecessary();
-                }
-                return;
-            }
+        container.setInfo(taskFragmentInfo);
+        if (taskFragmentInfo.isEmpty()) {
+            cleanupContainer(container, true /* shouldFinishDependent */);
+            updateCallbackIfNecessary();
         }
     }
 
     @Override
     public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
-        for (TaskFragmentContainer container : mContainers) {
-            if (container.getTaskFragmentToken().equals(taskFragmentInfo.getFragmentToken())) {
-                cleanupContainer(container, true /* shouldFinishDependent */);
-                updateCallbackIfNecessary();
-                return;
-            }
+        TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
+        if (container == null) {
+            return;
         }
+
+        cleanupContainer(container, true /* shouldFinishDependent */);
+        updateCallbackIfNecessary();
     }
 
     @Override
@@ -148,6 +149,7 @@
      * Checks if the activity start should be routed to a particular container. It can create a new
      * container for the activity and a new split container if necessary.
      */
+    // TODO(b/190433398): Break down into smaller functions.
     void onActivityCreated(@NonNull Activity launchedActivity) {
         final ComponentName componentName = launchedActivity.getComponentName();
 
@@ -201,6 +203,18 @@
             return;
         }
 
+        // Check if the split is already set.
+        final TaskFragmentContainer activityBelowContainer = getContainerWithActivity(
+                activityBelow.getActivityToken());
+        if (currentContainer != null && activityBelowContainer != null) {
+            final SplitContainer existingSplit = getActiveSplitForContainers(currentContainer,
+                    activityBelowContainer);
+            if (existingSplit != null) {
+                // There is already an active split with the activity below.
+                return;
+            }
+        }
+
         final ExtensionSplitPairRule splitPairRule = getSplitRule(
                 activityBelow.getComponentName(), componentName, splitRules);
         if (splitPairRule == null) {
@@ -213,6 +227,20 @@
         updateCallbackIfNecessary();
     }
 
+    private void onActivityConfigurationChanged(@NonNull Activity activity) {
+        final TaskFragmentContainer currentContainer = getContainerWithActivity(
+                activity.getActivityToken());
+
+        if (currentContainer != null) {
+            // Changes to activities in controllers are handled in
+            // onTaskFragmentParentInfoChanged
+            return;
+        }
+
+        // Check if activity requires a placeholder
+        launchPlaceholderIfNecessary(activity);
+    }
+
     /**
      * Returns a container that this activity is registered with. An activity can only belong to one
      * container, or no container at all.
@@ -324,7 +352,7 @@
     }
 
     /**
-     * Returns the top active split container that has the provided container.
+     * Returns the top active split container that has the provided container, if available.
      */
     @Nullable
     private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
@@ -339,6 +367,26 @@
     }
 
     /**
+     * Returns the active split that has the provided containers as primary and secondary or as
+     * secondary and primary, if available.
+     */
+    @Nullable
+    private SplitContainer getActiveSplitForContainers(
+            @NonNull TaskFragmentContainer firstContainer,
+            @NonNull TaskFragmentContainer secondContainer) {
+        for (int i = mSplitContainers.size() - 1; i >= 0; i--) {
+            SplitContainer splitContainer = mSplitContainers.get(i);
+            final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
+            final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
+            if ((firstContainer == secondary && secondContainer == primary)
+                    || (firstContainer == primary && secondContainer == secondary)) {
+                return splitContainer;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Checks if the container requires a placeholder and launches it if necessary.
      */
     private boolean launchPlaceholderIfNecessary(@NonNull TaskFragmentContainer container) {
@@ -480,7 +528,7 @@
     }
 
     @Nullable
-    private TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
+    TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
         for (TaskFragmentContainer container : mContainers) {
             if (container.getTaskFragmentToken().equals(fragmentToken)) {
                 return container;
@@ -546,6 +594,10 @@
 
         @Override
         public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+        }
+
+        @Override
+        public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
             // Calling after Activity#onCreate is complete to allow the app launch something
             // first. In case of a configured placeholder activity we want to make sure
             // that we don't launch it if an activity itself already requested something to be
@@ -576,5 +628,20 @@
         @Override
         public void onActivityDestroyed(Activity activity) {
         }
+
+        @Override
+        public void onActivityConfigurationChanged(Activity activity) {
+            SplitController.this.onActivityConfigurationChanged(activity);
+        }
+    }
+
+    /** Executor that posts on the main application thread. */
+    private static class MainThreadExecutor implements Executor {
+        private final Handler handler = new Handler(Looper.getMainLooper());
+
+        @Override
+        public void execute(Runnable r) {
+            handler.post(r);
+        }
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
index 381d6d7..90af72b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/SplitPresenter.java
@@ -23,6 +23,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.window.TaskFragmentCreationParams;
 import android.window.WindowContainerTransaction;
 
@@ -98,8 +99,6 @@
 
         final Rect parentBounds = getParentContainerBounds(primaryActivity);
         final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
-        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
-
         TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
                 primaryActivity.getActivityToken());
         if (primaryContainer == null) {
@@ -115,10 +114,13 @@
 
             wct.reparentActivityToTaskFragment(primaryContainer.getTaskFragmentToken(),
                     primaryActivity.getActivityToken());
+
+            primaryContainer.setLastRequestedBounds(primaryRectBounds);
         } else {
             resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
         }
 
+        final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
         TaskFragmentContainer secondaryContainer = mController.getContainerWithActivity(
                 secondaryActivity.getActivityToken());
         if (secondaryContainer == null || secondaryContainer == primaryContainer) {
@@ -134,6 +136,8 @@
 
             wct.reparentActivityToTaskFragment(secondaryContainer.getTaskFragmentToken(),
                     secondaryActivity.getActivityToken());
+
+            secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
         } else {
             resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
         }
@@ -177,6 +181,9 @@
                 activityIntent,
                 activityOptions);
 
+        primaryContainer.setLastRequestedBounds(primaryRectBounds);
+        secondaryContainer.setLastRequestedBounds(secondaryRectBounds);
+
         // TODO(b/190433398): The primary container and the secondary container should also be set
         // as adjacent (WCT#setAdjacentRoots) to make activities behind invisible.
 
@@ -199,7 +206,6 @@
         final Rect primaryRectBounds = getBoundsForPosition(POSITION_LEFT, parentBounds, rule);
         final Rect secondaryRectBounds = getBoundsForPosition(POSITION_RIGHT, parentBounds, rule);
 
-        // TODO(b/190433398): Check if the bounds actually changed.
         // If the task fragments are not registered yet, the positions will be updated after they
         // are created again.
         resizeTaskFragmentIfRegistered(wct, splitContainer.getPrimaryContainer(),
@@ -219,10 +225,27 @@
         if (container.getInfo() == null) {
             return;
         }
-        // TODO(b/190433398): Check if the bounds actually changed.
         resizeTaskFragment(wct, container.getTaskFragmentToken(), bounds);
     }
 
+    @Override
+    void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+            @Nullable Rect bounds) {
+        TaskFragmentContainer container = mController.getContainer(fragmentToken);
+        if (container == null) {
+            throw new IllegalStateException(
+                    "Resizing a task fragment that is not registered with controller.");
+        }
+
+        if (container.areLastRequestedBoundsEqual(bounds)) {
+            // Return early if the provided bounds were already requested
+            return;
+        }
+
+        container.setLastRequestedBounds(bounds);
+        super.resizeTaskFragment(wct, fragmentToken, bounds);
+    }
+
     boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
         final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer());
         return shouldShowSideBySide(parentBounds, splitContainer.getSplitPairRule());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
index 3cf37a6..368adef 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/organizer/TaskFragmentContainer.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityThread;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
 import android.window.TaskFragmentInfo;
@@ -60,6 +61,11 @@
     private boolean mIsFinished;
 
     /**
+     * Bounds that were requested last via {@link android.window.WindowContainerTransaction}.
+     */
+    private final Rect mLastRequestedBounds = new Rect();
+
+    /**
      * Creates a container with an existing activity that will be re-parented to it in a window
      * container transaction.
      */
@@ -199,4 +205,23 @@
     boolean isFinished() {
         return mIsFinished;
     }
+
+    /**
+     * Checks if last requested bounds are equal to the provided value.
+     */
+    boolean areLastRequestedBoundsEqual(@Nullable Rect bounds) {
+        return (bounds == null && mLastRequestedBounds.isEmpty())
+                || mLastRequestedBounds.equals(bounds);
+    }
+
+    /**
+     * Updates the last requested bounds.
+     */
+    void setLastRequestedBounds(@Nullable Rect bounds) {
+        if (bounds == null) {
+            mLastRequestedBounds.setEmpty();
+        } else {
+            mLastRequestedBounds.set(bounds);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml b/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml
new file mode 100644
index 0000000..4f56e0f
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/one_handed_tutorial_background_color.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/colorSurfaceVariant"/>
+</selector>
diff --git a/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png b/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png
deleted file mode 100644
index 6c1f1cf..0000000
--- a/libs/WindowManager/Shell/res/drawable-hdpi/one_handed_tutorial.png
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Shell/res/drawable/one_handed_tutorial_icon.xml b/libs/WindowManager/Shell/res/drawable/one_handed_tutorial_icon.xml
new file mode 100644
index 0000000..b32f34e
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/one_handed_tutorial_icon.xml
@@ -0,0 +1,14 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="60dp"
+    android:viewportWidth="32"
+    android:viewportHeight="60">
+  <path
+      android:pathData="M1.9703,30.5041C1.9703,28.295 3.7612,26.5042 5.9703,26.5042H25.5551C27.7642,26.5042 29.5551,28.295 29.5551,30.5042V54.0296C29.5551,56.2387 27.7642,58.0296 25.5551,58.0296H5.9703C3.7612,58.0296 1.9703,56.2387 1.9703,54.0296V30.5041Z"
+      android:fillColor="#000000"
+      android:fillAlpha="0.16"/>
+  <path
+      android:pathData="M25.5254,2H6C3.7909,2 2,3.7909 2,6V54C2,56.2091 3.7909,58 6,58H25.5254C27.7346,58 29.5254,56.2091 29.5254,54V6C29.5254,3.7909 27.7346,2 25.5254,2ZM6,0C2.6863,0 0,2.6863 0,6V54C0,57.3137 2.6863,60 6,60H25.5254C28.8391,60 31.5254,57.3137 31.5254,54V6C31.5254,2.6863 28.8391,0 25.5254,0H6ZM12.2034,47.2336L12.8307,47.861L15.3178,45.3783V52.1277H16.2076V45.3783L18.6903,47.8654L19.322,47.2336L15.7627,43.6743L12.2034,47.2336ZM19.7034,55.0742H11.822V56.552H19.7034V55.0742Z"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml b/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml
index 0190aad..d29ed8b 100644
--- a/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml
+++ b/libs/WindowManager/Shell/res/layout/one_handed_tutorial.xml
@@ -31,7 +31,7 @@
         android:layout_marginTop="6dp"
         android:layout_marginBottom="0dp"
         android:gravity="center_horizontal"
-        android:src="@drawable/one_handed_tutorial"
+        android:src="@drawable/one_handed_tutorial_icon"
         android:scaleType="centerInside" />
 
     <TextView
@@ -45,7 +45,6 @@
         android:fontFamily="google-sans-medium"
         android:text="@string/one_handed_tutorial_title"
         android:textSize="16sp"
-        android:textStyle="bold"
         android:textColor="@android:color/white"/>
 
     <TextView
@@ -54,8 +53,8 @@
         android:layout_height="wrap_content"
         android:layout_marginTop="6dp"
         android:layout_marginBottom="0dp"
-        android:layout_marginStart="46dp"
-        android:layout_marginEnd="46dp"
+        android:layout_marginStart="60dp"
+        android:layout_marginEnd="60dp"
         android:gravity="center_horizontal"
         android:fontFamily="roboto-regular"
         android:text="@string/one_handed_tutorial_description"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d460195..36b8923 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -45,6 +45,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -205,6 +206,7 @@
      * between bubble activities without needing both to be alive at the same time.
      */
     private SurfaceView mAnimatingOutSurfaceView;
+    private boolean mAnimatingOutSurfaceReady;
 
     /** Container for the animating-out SurfaceView. */
     private FrameLayout mAnimatingOutSurfaceContainer;
@@ -808,6 +810,20 @@
         mAnimatingOutSurfaceView.setZOrderOnTop(true);
         mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius);
         mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
+        mAnimatingOutSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
+            @Override
+            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {}
+
+            @Override
+            public void surfaceCreated(SurfaceHolder surfaceHolder) {
+                mAnimatingOutSurfaceReady = true;
+            }
+
+            @Override
+            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+                mAnimatingOutSurfaceReady = false;
+            }
+        });
         mAnimatingOutSurfaceContainer.addView(mAnimatingOutSurfaceView);
 
         mAnimatingOutSurfaceContainer.setPadding(
@@ -2652,7 +2668,7 @@
                 return;
             }
 
-            if (!mIsExpanded) {
+            if (!mIsExpanded || !mAnimatingOutSurfaceReady) {
                 onComplete.accept(false);
                 return;
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 6f63369..ba59e07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -33,6 +33,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
@@ -198,6 +199,7 @@
     public class PerDisplay {
         final int mDisplayId;
         final InsetsState mInsetsState = new InsetsState();
+        final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
         protected final DisplayWindowInsetsControllerImpl mInsetsControllerImpl =
                 new DisplayWindowInsetsControllerImpl();
         InsetsSourceControl mImeSourceControl = null;
@@ -327,8 +329,10 @@
          */
         private void setVisibleDirectly(boolean visible) {
             mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
+            mRequestedVisibilities.setVisibility(InsetsState.ITYPE_IME, visible);
             try {
-                mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+                mWmService.updateDisplayWindowRequestedVisibilities(mDisplayId,
+                        mRequestedVisibilities);
             } catch (RemoteException e) {
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 9bcc3ac..5b9d7b80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -151,10 +151,8 @@
                 final Rect rightHitRegion = new Rect();
                 final Rect rightDrawRegion = bottomOrRightBounds;
 
-                displayRegion.splitVertically(leftHitRegion, fullscreenHitRegion, rightHitRegion);
+                displayRegion.splitVertically(leftHitRegion, rightHitRegion);
 
-                mTargets.add(
-                        new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
                 mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
                 mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));
 
@@ -165,10 +163,8 @@
                 final Rect bottomDrawRegion = bottomOrRightBounds;
 
                 displayRegion.splitHorizontally(
-                        topHitRegion, fullscreenHitRegion, bottomHitRegion);
+                        topHitRegion, bottomHitRegion);
 
-                mTargets.add(
-                        new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
                 mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
                 mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
             }
@@ -269,7 +265,6 @@
          * Updates the session data based on the current state of the system.
          */
         void update() {
-
             List<ActivityManager.RunningTaskInfo> tasks =
                     mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
             if (!tasks.isEmpty()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index 481b948..3ccb9e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -20,7 +20,6 @@
 import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.util.Log;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.window.DisplayAreaAppearedInfo;
@@ -30,7 +29,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.core.content.ContextCompat;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.wm.shell.R;
@@ -48,14 +46,17 @@
 public class OneHandedBackgroundPanelOrganizer extends DisplayAreaOrganizer
         implements OneHandedTransitionCallback {
     private static final String TAG = "OneHandedBackgroundPanelOrganizer";
+    private static final int THEME_COLOR_OFFSET = 10;
 
+    private final Context mContext;
     private final Object mLock = new Object();
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
-    private final float[] mDefaultColor;
     private final Executor mMainExecutor;
     private final OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
 
+    private float[] mDefaultColor;
+
     /**
      * The background to distinguish the boundary of translated windows and empty region when
      * one handed mode triggered.
@@ -88,15 +89,14 @@
     public OneHandedBackgroundPanelOrganizer(Context context, DisplayLayout displayLayout,
             Executor executor) {
         super(executor);
+        mContext = context;
         // Ensure the mBkgBounds is portrait, due to OHM only support on portrait
         if (displayLayout.height() > displayLayout.width()) {
             mBkgBounds = new Rect(0, 0, displayLayout.width(), displayLayout.height());
         } else {
             mBkgBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
         }
-        final int defaultColor = ContextCompat.getColor(context, R.color.GM2_grey_800);
-        mDefaultColor = new float[]{Color.red(defaultColor) / 255.0f,
-                Color.green(defaultColor) / 255.0f, Color.blue(defaultColor) / 255.0f};
+        updateThemeColors();
         mMainExecutor = executor;
         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
     }
@@ -170,7 +170,6 @@
             }
 
             if (getBackgroundSurface() == null) {
-                Log.w(TAG, "mBackgroundSurface is null !");
                 return;
             }
 
@@ -201,6 +200,30 @@
         }
     }
 
+    /**
+     * onConfigurationChanged events for updating tutorial text.
+     */
+    public void onConfigurationChanged() {
+        synchronized (mLock) {
+            if (mBackgroundSurface == null) {
+                getBackgroundSurface();
+            } else {
+                removeBackgroundPanelLayer();
+            }
+            updateThemeColors();
+            showBackgroundPanelLayer();
+        }
+    }
+
+    private void updateThemeColors() {
+        synchronized (mLock) {
+            final int themeColor = mContext.getColor(R.color.one_handed_tutorial_background_color);
+            mDefaultColor = new float[]{(Color.red(themeColor) - THEME_COLOR_OFFSET) / 255.0f,
+                    (Color.green(themeColor) - THEME_COLOR_OFFSET) / 255.0f,
+                    (Color.blue(themeColor) - THEME_COLOR_OFFSET) / 255.0f};
+        }
+    }
+
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 2038cff..09cde38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -658,12 +658,13 @@
     }
 
     private void onConfigChanged(Configuration newConfig) {
-        if (mTutorialHandler == null) {
+        if (mTutorialHandler == null || mBackgroundPanelOrganizer == null) {
             return;
         }
         if (!mIsOneHandedEnabled || newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
             return;
         }
+        mBackgroundPanelOrganizer.onConfigurationChanged();
         mTutorialHandler.onConfigurationChanged();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index d0206a4..97e04b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -25,8 +25,11 @@
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
 
+import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.os.SystemProperties;
@@ -35,9 +38,13 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
+import android.view.animation.LinearInterpolator;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.appcompat.view.ContextThemeWrapper;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
@@ -53,11 +60,14 @@
 public class OneHandedTutorialHandler implements OneHandedTransitionCallback,
         OneHandedState.OnStateChangedListener {
     private static final String TAG = "OneHandedTutorialHandler";
-    private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
-            "persist.debug.one_handed_offset_percentage";
+    private static final String OFFSET_PERCENTAGE = "persist.debug.one_handed_offset_percentage";
+    private static final String TRANSLATE_ANIMATION_DURATION =
+            "persist.debug.one_handed_translate_animation_duration";
+    private static final float START_TRANSITION_FRACTION = 0.7f;
 
     private final float mTutorialHeightRatio;
     private final WindowManager mWindowManager;
+    private final OneHandedAnimationCallback mAnimationCallback;
 
     private @OneHandedState.State int mCurrentState;
     private int mTutorialAreaHeight;
@@ -67,7 +77,9 @@
     private @Nullable View mTutorialView;
     private @Nullable ViewGroup mTargetViewContainer;
 
-    private final OneHandedAnimationCallback mAnimationCallback;
+    private float mAlphaTransitionStart;
+    private ValueAnimator mAlphaAnimator;
+    private int mAlphaAnimationDurationMs;
 
     public OneHandedTutorialHandler(Context context, WindowManager windowManager) {
         mContext = context;
@@ -75,15 +87,35 @@
         final float offsetPercentageConfig = context.getResources().getFraction(
                 R.fraction.config_one_handed_offset, 1, 1);
         final int sysPropPercentageConfig = SystemProperties.getInt(
-                ONE_HANDED_MODE_OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
+                OFFSET_PERCENTAGE, Math.round(offsetPercentageConfig * 100.0f));
         mTutorialHeightRatio = sysPropPercentageConfig / 100.0f;
+        final int animationDuration = context.getResources().getInteger(
+                R.integer.config_one_handed_translate_animation_duration);
+        mAlphaAnimationDurationMs = SystemProperties.getInt(TRANSLATE_ANIMATION_DURATION,
+                animationDuration);
         mAnimationCallback = new OneHandedAnimationCallback() {
             @Override
+            public void onOneHandedAnimationCancel(
+                    OneHandedAnimationController.OneHandedTransitionAnimator animator) {
+                if (mAlphaAnimator != null) {
+                    mAlphaAnimator.cancel();
+                }
+            }
+
+            @Override
             public void onAnimationUpdate(float xPos, float yPos) {
                 if (!isAttached()) {
                     return;
                 }
-                mTargetViewContainer.setTranslationY(yPos - mTutorialAreaHeight);
+                if (yPos < mAlphaTransitionStart) {
+                    checkTransitionEnd();
+                    return;
+                }
+                if (mAlphaAnimator == null || mAlphaAnimator.isStarted()
+                        || mAlphaAnimator.isRunning()) {
+                    return;
+                }
+                mAlphaAnimator.start();
             }
         };
     }
@@ -94,12 +126,16 @@
         switch (newState) {
             case STATE_ENTERING:
                 createViewAndAttachToWindow(mContext);
+                updateThemeColor();
+                setupAlphaTransition(true /* isEntering */);
                 break;
             case STATE_ACTIVE:
-            case STATE_EXITING:
-                // no - op
+                checkTransitionEnd();
+                setupAlphaTransition(false /* isEntering */);
                 break;
+            case STATE_EXITING:
             case STATE_NONE:
+                checkTransitionEnd();
                 removeTutorialFromWindowManager();
                 break;
             default:
@@ -119,6 +155,7 @@
             mDisplayBounds = new Rect(0, 0, displayLayout.height(), displayLayout.width());
         }
         mTutorialAreaHeight = Math.round(mDisplayBounds.height() * mTutorialHeightRatio);
+        mAlphaTransitionStart = mTutorialAreaHeight * START_TRANSITION_FRACTION;
     }
 
     @VisibleForTesting
@@ -129,6 +166,7 @@
         mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null);
         mTargetViewContainer = new FrameLayout(context);
         mTargetViewContainer.setClipChildren(false);
+        mTargetViewContainer.setAlpha(mCurrentState == STATE_ACTIVE ? 1.0f : 0.0f);
         mTargetViewContainer.addView(mTutorialView);
         mTargetViewContainer.setLayerType(LAYER_TYPE_HARDWARE, null);
 
@@ -192,6 +230,52 @@
         removeTutorialFromWindowManager();
         if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
             createViewAndAttachToWindow(mContext);
+            updateThemeColor();
+            checkTransitionEnd();
+        }
+    }
+
+    private void updateThemeColor() {
+        if (mTutorialView == null) {
+            return;
+        }
+
+        final Context themedContext = new ContextThemeWrapper(mTutorialView.getContext(),
+                com.android.internal.R.style.Theme_DeviceDefault_DayNight);
+        final int textColorPrimary;
+        final int themedTextColorSecondary;
+        TypedArray ta = themedContext.obtainStyledAttributes(new int[]{
+                com.android.internal.R.attr.textColorPrimary,
+                com.android.internal.R.attr.textColorSecondary});
+        textColorPrimary = ta.getColor(0, 0);
+        themedTextColorSecondary = ta.getColor(1, 0);
+        ta.recycle();
+
+        final ImageView iconView = mTutorialView.findViewById(R.id.one_handed_tutorial_image);
+        iconView.setImageTintList(ColorStateList.valueOf(textColorPrimary));
+
+        final TextView tutorialTitle = mTutorialView.findViewById(R.id.one_handed_tutorial_title);
+        final TextView tutorialDesc = mTutorialView.findViewById(
+                R.id.one_handed_tutorial_description);
+        tutorialTitle.setTextColor(textColorPrimary);
+        tutorialDesc.setTextColor(themedTextColorSecondary);
+    }
+
+    private void setupAlphaTransition(boolean isEntering) {
+        final float start = isEntering ? 0.0f : 1.0f;
+        final float end = isEntering ? 1.0f : 0.0f;
+        mAlphaAnimator = ValueAnimator.ofFloat(start, end);
+        mAlphaAnimator.setInterpolator(new LinearInterpolator());
+        mAlphaAnimator.setDuration(mAlphaAnimationDurationMs);
+        mAlphaAnimator.addUpdateListener(
+                animator -> mTargetViewContainer.setAlpha((float) animator.getAnimatedValue()));
+    }
+
+    private void checkTransitionEnd() {
+        if (mAlphaAnimator != null && mAlphaAnimator.isRunning()) {
+            mAlphaAnimator.end();
+            mAlphaAnimator.removeAllUpdateListeners();
+            mAlphaAnimator = null;
         }
     }
 
@@ -206,5 +290,9 @@
         pw.println(mDisplayBounds);
         pw.print(innerPrefix + "mTutorialAreaHeight=");
         pw.println(mTutorialAreaHeight);
+        pw.print(innerPrefix + "mAlphaTransitionStart=");
+        pw.println(mAlphaTransitionStart);
+        pw.print(innerPrefix + "mAlphaAnimationDurationMs=");
+        pw.println(mAlphaAnimationDurationMs);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index d6afeba..e86462f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -17,10 +17,13 @@
 package com.android.wm.shell.splitscreen;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
 
+import java.util.concurrent.Executor;
+
 /**
  * Interface to engage split-screen feature.
  * TODO: Figure out which of these are actually needed outside of the Shell
@@ -53,10 +56,18 @@
 
     /** Callback interface for listening to changes in a split-screen stage. */
     interface SplitScreenListener {
-        void onStagePositionChanged(@StageType int stage, @SplitPosition int position);
-        void onTaskStageChanged(int taskId, @StageType int stage, boolean visible);
+        default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
+        default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+        default void onSplitVisibilityChanged(boolean visible) {}
     }
 
+    /** Registers listener that gets split screen callback. */
+    void registerSplitScreenListener(@NonNull SplitScreenListener listener,
+            @NonNull Executor executor);
+
+    /** Unregisters listener that gets split screen callback. */
+    void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
+
     /**
      * Returns a binder that can be passed to an external process to manipulate SplitScreen.
      */
@@ -70,6 +81,12 @@
      */
     void onKeyguardOccludedChanged(boolean occluded);
 
+    /**
+     * Called when the visibility of the keyguard changes.
+     * @param showing Indicates if the keyguard is now visible.
+     */
+    void onKeyguardVisibilityChanged(boolean showing);
+
     /** Get a string representation of a stage type */
     static String stageTypeToString(@StageType int stage) {
         switch (stage) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 71f23a7..7804c77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -36,6 +36,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.RemoteAnimationAdapter;
@@ -64,6 +65,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 /**
  * Class manages split-screen multitasking mode and implements the main interface
@@ -170,6 +172,10 @@
         mStageCoordinator.onKeyguardOccludedChanged(occluded);
     }
 
+    public void onKeyguardVisibilityChanged(boolean showing) {
+        mStageCoordinator.onKeyguardVisibilityChanged(showing);
+    }
+
     public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
         mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
     }
@@ -293,6 +299,38 @@
     @ExternalThread
     private class SplitScreenImpl implements SplitScreen {
         private ISplitScreenImpl mISplitScreen;
+        private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
+        private final SplitScreen.SplitScreenListener mListener = new SplitScreenListener() {
+            @Override
+            public void onStagePositionChanged(int stage, int position) {
+                for (int i = 0; i < mExecutors.size(); i++) {
+                    final int index = i;
+                    mExecutors.valueAt(index).execute(() -> {
+                        mExecutors.keyAt(index).onStagePositionChanged(stage, position);
+                    });
+                }
+            }
+
+            @Override
+            public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+                for (int i = 0; i < mExecutors.size(); i++) {
+                    final int index = i;
+                    mExecutors.valueAt(index).execute(() -> {
+                        mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
+                    });
+                }
+            }
+
+            @Override
+            public void onSplitVisibilityChanged(boolean visible) {
+                for (int i = 0; i < mExecutors.size(); i++) {
+                    final int index = i;
+                    mExecutors.valueAt(index).execute(() -> {
+                        mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
+                    });
+                }
+            }
+        };
 
         @Override
         public ISplitScreen createExternalInterface() {
@@ -309,6 +347,41 @@
                 SplitScreenController.this.onKeyguardOccludedChanged(occluded);
             });
         }
+
+        @Override
+        public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
+            if (mExecutors.containsKey(listener)) return;
+
+            mMainExecutor.execute(() -> {
+                if (mExecutors.size() == 0) {
+                    SplitScreenController.this.registerSplitScreenListener(mListener);
+                }
+
+                mExecutors.put(listener, executor);
+            });
+
+            executor.execute(() -> {
+                mStageCoordinator.sendStatusToListener(listener);
+            });
+        }
+
+        @Override
+        public void unregisterSplitScreenListener(SplitScreenListener listener) {
+            mMainExecutor.execute(() -> {
+                mExecutors.remove(listener);
+
+                if (mExecutors.size() == 0) {
+                    SplitScreenController.this.unregisterSplitScreenListener(mListener);
+                }
+            });
+        }
+
+        @Override
+        public void onKeyguardVisibilityChanged(boolean showing) {
+            mMainExecutor.execute(() -> {
+                SplitScreenController.this.onKeyguardVisibilityChanged(showing);
+            });
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 6f23ae5..962b5d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -136,7 +136,10 @@
     /** Whether the device is supporting legacy split or not. */
     private boolean mUseLegacySplit;
 
-    @SplitScreen.StageType int mDismissTop = NO_DISMISS;
+    @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
+
+    /** The target stage to dismiss to when unlock after folded. */
+    @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
 
     private final Runnable mOnTransitionAnimationComplete = () -> {
         // If still playing, let it finish.
@@ -443,6 +446,13 @@
         mKeyguardOccluded = occluded;
     }
 
+    void onKeyguardVisibilityChanged(boolean showing) {
+        if (!showing && mMainStage.isActive()
+                && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
+            exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage);
+        }
+    }
+
     void exitSplitScreen() {
         exitSplitScreen(null /* childrenToTop */);
     }
@@ -458,6 +468,7 @@
         mTaskOrganizer.applyTransaction(wct);
         // Reset divider position.
         mSplitLayout.resetDividerPosition();
+        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
     }
 
     /**
@@ -501,16 +512,21 @@
     void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
         if (mListeners.contains(listener)) return;
         mListeners.add(listener);
-        listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
-        listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
-        mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
-        mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+        sendStatusToListener(listener);
     }
 
     void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
         mListeners.remove(listener);
     }
 
+    void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
+        listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
+        listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
+        listener.onSplitVisibilityChanged(isSplitScreenVisible());
+        mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
+        mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
+    }
+
     private void sendOnStagePositionChanged() {
         for (int i = mListeners.size() - 1; i >= 0; --i) {
             final SplitScreen.SplitScreenListener l = mListeners.get(i);
@@ -535,6 +551,13 @@
         }
     }
 
+    private void sendSplitVisibilityChanged() {
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            final SplitScreen.SplitScreenListener l = mListeners.get(i);
+            l.onSplitVisibilityChanged(mDividerVisible);
+        }
+    }
+
     private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
         if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
             mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
@@ -574,6 +597,7 @@
         } else {
             mSplitLayout.release();
         }
+        sendSplitVisibilityChanged();
     }
 
     private void onStageVisibilityChanged(StageListenerImpl stageListener) {
@@ -795,8 +819,13 @@
     }
 
     private void onFoldedStateChanged(boolean folded) {
-        if (folded && mMainStage.isActive()) {
-            exitSplitScreen(mMainStage);
+        mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
+        if (!folded) return;
+
+        if (mMainStage.isFocused()) {
+            mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
+        } else if (mSideStage.isFocused()) {
+            mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 4f73fee..c47353a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -97,6 +97,15 @@
         return mChildrenTaskInfo.contains(taskId);
     }
 
+    /** @return {@code true} if this listener contains the currently focused task. */
+    boolean isFocused() {
+        if (mRootTaskInfo.isFocused) return true;
+        for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+            if (mChildrenTaskInfo.valueAt(i).isFocused) return true;
+        }
+        return false;
+    }
+
     @Override
     @CallSuper
     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 56ad2be..dff5577 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -23,6 +23,7 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
 import android.annotation.ColorInt;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.app.ActivityThread;
 import android.content.BroadcastReceiver;
@@ -64,6 +65,7 @@
 
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.IntPredicate;
 import java.util.function.IntSupplier;
 import java.util.function.Supplier;
 import java.util.function.UnaryOperator;
@@ -132,7 +134,6 @@
      * @param splashScreenViewConsumer Receiving the SplashScreenView object, which will also be
      *                                 executed on splash screen thread. Note that the view can be
      *                                 null if failed.
-     * @param bgColorConsumer Receiving the background color once it's estimated complete.
      */
     void createContentView(Context context, @StartingWindowType int suggestType, ActivityInfo info,
             int taskId, Consumer<SplashScreenView> splashScreenViewConsumer) {
@@ -209,9 +210,9 @@
     }
 
     private static int estimateWindowBGColor(Drawable themeBGDrawable) {
-        final DrawableColorTester themeBGTester =
-                new DrawableColorTester(themeBGDrawable, true /* filterTransparent */);
-        if (themeBGTester.nonTransparentRatio() == 0) {
+        final DrawableColorTester themeBGTester = new DrawableColorTester(
+                themeBGDrawable, DrawableColorTester.TRANSPARENT_FILTER /* filterType */);
+        if (themeBGTester.passFilterRatio() == 0) {
             // the window background is transparent, unable to draw
             Slog.w(TAG, "Window background is transparent, fill background with black color");
             return getSystemBGColor();
@@ -414,7 +415,8 @@
             final ColorCache.IconColor iconColor = mColorCache.getIconColor(
                     mActivityInfo.packageName, mActivityInfo.getIconResource(),
                     mLastPackageContextConfigHash,
-                    () -> new DrawableColorTester(iconForeground, true /* filterTransparent */),
+                    () -> new DrawableColorTester(iconForeground,
+                            DrawableColorTester.TRANSLUCENT_FILTER /* filterType */),
                     () -> new DrawableColorTester(adaptiveIconDrawable.getBackground()));
 
             if (DEBUG) {
@@ -443,7 +445,7 @@
                 // Reference AdaptiveIcon description, outer is 108 and inner is 72, so we
                 // scale by 192/160 if we only draw adaptiveIcon's foreground.
                 final float noBgScale =
-                        iconColor.mFgNonTransparentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
+                        iconColor.mFgNonTranslucentRatio < ENLARGE_FOREGROUND_ICON_THRESHOLD
                                 ? NO_BACKGROUND_SCALE : 1f;
                 // Using AdaptiveIconDrawable here can help keep the shape consistent with the
                 // current settings.
@@ -545,13 +547,26 @@
     }
 
     private static class DrawableColorTester {
+        private static final int NO_ALPHA_FILTER = 0;
+        // filter out completely invisible pixels
+        private static final int TRANSPARENT_FILTER = 1;
+        // filter out translucent and invisible pixels
+        private static final int TRANSLUCENT_FILTER = 2;
+
+        @IntDef(flag = true, value = {
+                NO_ALPHA_FILTER,
+                TRANSPARENT_FILTER,
+                TRANSLUCENT_FILTER
+        })
+        private @interface QuantizerFilterType {}
+
         private final ColorTester mColorChecker;
 
         DrawableColorTester(Drawable drawable) {
-            this(drawable, false /* filterTransparent */);
+            this(drawable, NO_ALPHA_FILTER /* filterType */);
         }
 
-        DrawableColorTester(Drawable drawable, boolean filterTransparent) {
+        DrawableColorTester(Drawable drawable, @QuantizerFilterType int filterType) {
             // Some applications use LayerDrawable for their windowBackground. To ensure that we
             // only get the real background, so that the color is not affected by the alpha of the
             // upper layer, try to get the lower layer here. This can also speed up the calculation.
@@ -570,12 +585,12 @@
             } else {
                 mColorChecker = drawable instanceof ColorDrawable
                         ? new SingleColorTester((ColorDrawable) drawable)
-                        : new ComplexDrawableTester(drawable, filterTransparent);
+                        : new ComplexDrawableTester(drawable, filterType);
             }
         }
 
-        public float nonTransparentRatio() {
-            return mColorChecker.nonTransparentRatio();
+        public float passFilterRatio() {
+            return mColorChecker.passFilterRatio();
         }
 
         public boolean isComplexColor() {
@@ -594,7 +609,7 @@
          * A help class to check the color information from a Drawable.
          */
         private interface ColorTester {
-            float nonTransparentRatio();
+            float passFilterRatio();
 
             boolean isComplexColor();
 
@@ -622,7 +637,7 @@
             }
 
             @Override
-            public float nonTransparentRatio() {
+            public float passFilterRatio() {
                 final int alpha = mColorDrawable.getAlpha();
                 return (float) (alpha / 255);
             }
@@ -651,15 +666,21 @@
             private static final int MAX_BITMAP_SIZE = 40;
             private final Palette mPalette;
             private final boolean mFilterTransparent;
-            private static final TransparentFilterQuantizer TRANSPARENT_FILTER_QUANTIZER =
-                    new TransparentFilterQuantizer();
+            private static final AlphaFilterQuantizer ALPHA_FILTER_QUANTIZER =
+                    new AlphaFilterQuantizer();
 
-            ComplexDrawableTester(Drawable drawable, boolean filterTransparent) {
+            /**
+             * @param drawable The test target.
+             * @param filterType Targeting to filter out transparent or translucent pixels,
+             *                   this would be needed if want to check
+             *                   {@link #passFilterRatio()}, also affecting the estimated result
+             *                   of the dominant color.
+             */
+            ComplexDrawableTester(Drawable drawable, @QuantizerFilterType int filterType) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ComplexDrawableTester");
                 final Rect initialBounds = drawable.copyBounds();
                 int width = drawable.getIntrinsicWidth();
                 int height = drawable.getIntrinsicHeight();
-
                 // Some drawables do not have intrinsic dimensions
                 if (width <= 0 || height <= 0) {
                     width = MAX_BITMAP_SIZE;
@@ -680,9 +701,10 @@
                 // The Palette API will ignore Alpha, so it cannot handle transparent pixels, but
                 // sometimes we will need this information to know if this Drawable object is
                 // transparent.
-                mFilterTransparent = filterTransparent;
+                mFilterTransparent = filterType != NO_ALPHA_FILTER;
                 if (mFilterTransparent) {
-                    builder = new Palette.Builder(bitmap, TRANSPARENT_FILTER_QUANTIZER)
+                    ALPHA_FILTER_QUANTIZER.setFilter(filterType);
+                    builder = new Palette.Builder(bitmap, ALPHA_FILTER_QUANTIZER)
                             .maximumColorCount(5);
                 } else {
                     builder = new Palette.Builder(bitmap, null)
@@ -694,8 +716,8 @@
             }
 
             @Override
-            public float nonTransparentRatio() {
-                return mFilterTransparent ? TRANSPARENT_FILTER_QUANTIZER.mNonTransparentRatio : 1;
+            public float passFilterRatio() {
+                return mFilterTransparent ? ALPHA_FILTER_QUANTIZER.mPassFilterRatio : 1;
             }
 
             @Override
@@ -726,17 +748,34 @@
                 return true;
             }
 
-            private static class TransparentFilterQuantizer implements Quantizer {
+            private static class AlphaFilterQuantizer implements Quantizer {
                 private static final int NON_TRANSPARENT = 0xFF000000;
                 private final Quantizer mInnerQuantizer = new VariationalKMeansQuantizer();
-                private float mNonTransparentRatio;
+                private final IntPredicate mTransparentFilter = i -> (i & NON_TRANSPARENT) != 0;
+                private final IntPredicate mTranslucentFilter = i ->
+                        (i & NON_TRANSPARENT) == NON_TRANSPARENT;
+
+                private IntPredicate mFilter = mTransparentFilter;
+                private float mPassFilterRatio;
+
+                void setFilter(@QuantizerFilterType int filterType) {
+                    switch (filterType) {
+                        case TRANSLUCENT_FILTER:
+                            mFilter = mTranslucentFilter;
+                            break;
+                        case TRANSPARENT_FILTER:
+                        default:
+                            mFilter = mTransparentFilter;
+                            break;
+                    }
+                }
 
                 @Override
                 public void quantize(final int[] pixels, final int maxColors) {
-                    mNonTransparentRatio = 0;
+                    mPassFilterRatio = 0;
                     int realSize = 0;
                     for (int i = pixels.length - 1; i > 0; i--) {
-                        if ((pixels[i] & NON_TRANSPARENT) != 0) {
+                        if (mFilter.test(pixels[i])) {
                             realSize++;
                         }
                     }
@@ -747,11 +786,11 @@
                         mInnerQuantizer.quantize(pixels, maxColors);
                         return;
                     }
-                    mNonTransparentRatio = (float) realSize / pixels.length;
+                    mPassFilterRatio = (float) realSize / pixels.length;
                     final int[] samplePixels = new int[realSize];
                     int rowIndex = 0;
                     for (int i = pixels.length - 1; i > 0; i--) {
-                        if ((pixels[i] & NON_TRANSPARENT) == NON_TRANSPARENT) {
+                        if (mFilter.test(pixels[i])) {
                             samplePixels[rowIndex] = pixels[i];
                             rowIndex++;
                         }
@@ -810,16 +849,16 @@
             final int mBgColor;
             final boolean mIsBgComplex;
             final boolean mIsBgGrayscale;
-            final float mFgNonTransparentRatio;
+            final float mFgNonTranslucentRatio;
 
             IconColor(int hash, int fgColor, int bgColor, boolean isBgComplex,
-                    boolean isBgGrayscale, float fgNonTransparentRatio) {
+                    boolean isBgGrayscale, float fgNonTranslucnetRatio) {
                 super(hash);
                 mFgColor = fgColor;
                 mBgColor = bgColor;
                 mIsBgComplex = isBgComplex;
                 mIsBgGrayscale = isBgGrayscale;
-                mFgNonTransparentRatio = fgNonTransparentRatio;
+                mFgNonTranslucentRatio = fgNonTranslucnetRatio;
             }
         }
 
@@ -905,7 +944,7 @@
             final DrawableColorTester bgTester = bgColorTesterSupplier.get();
             final IconColor iconColor = new IconColor(hash, fgTester.getDominateColor(),
                     bgTester.getDominateColor(), bgTester.isComplexColor(), bgTester.isGrayscale(),
-                    fgTester.nonTransparentRatio());
+                    fgTester.passFilterRatio());
             colors.mIconColors[leastUsedIndex[0]] = iconColor;
             return iconColor;
         }
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 243751fe..fc7c86d 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
@@ -168,16 +168,14 @@
     void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
             @StartingWindowType int suggestType) {
         final RunningTaskInfo taskInfo = windowInfo.taskInfo;
-        final ActivityInfo activityInfo = taskInfo.topActivityInfo;
-        if (activityInfo == null) {
+        final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+                ? windowInfo.targetActivityInfo
+                : taskInfo.topActivityInfo;
+        if (activityInfo == null || activityInfo.packageName == null) {
             return;
         }
 
         final int displayId = taskInfo.displayId;
-        if (activityInfo.packageName == null) {
-            return;
-        }
-
         final int taskId = taskInfo.taskId;
         Context context = mContext;
         // replace with the default theme if the application didn't set
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 6052d3d..7d011e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -72,6 +72,7 @@
 import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
@@ -205,7 +206,7 @@
         final SurfaceControl surfaceControl = new SurfaceControl();
         final ClientWindowFrames tmpFrames = new ClientWindowFrames();
 
-        final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
+        final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
 
         final TaskDescription taskDescription;
@@ -225,13 +226,14 @@
                 delayRemovalTime, topWindowInsetsState, clearWindowHandler, splashScreenExecutor);
         final Window window = snapshotSurface.mWindow;
 
-        final InsetsState mTmpInsetsState = new InsetsState();
+        final InsetsState tmpInsetsState = new InsetsState();
+        final InsetsVisibilities tmpRequestedVisibilities = new InsetsVisibilities();
         final InputChannel tmpInputChannel = new InputChannel();
 
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
             final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
-                    mTmpInsetsState, tmpInputChannel, mTmpInsetsState, mTempControls);
+                    tmpRequestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             if (res < 0) {
                 Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
@@ -244,8 +246,8 @@
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
             session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, -1,
-                    tmpFrames, tmpMergedConfiguration, surfaceControl, mTmpInsetsState,
-                    mTempControls, TMP_SURFACE_SIZE);
+                    tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
+                    tmpControls, TMP_SURFACE_SIZE);
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         } catch (RemoteException e) {
             snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 7f42fe9..01134a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -59,6 +59,7 @@
 import android.view.Choreographer;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
+import android.view.WindowManager;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
 import android.view.animation.Transformation;
@@ -132,6 +133,15 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                 "start default transition animation, info = %s", info);
+
+        // Fallback for screen wake. This just immediately finishes since there is no
+        // animation for screen-wake.
+        if (info.getType() == WindowManager.TRANSIT_WAKE) {
+            startTransaction.apply();
+            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            return true;
+        }
+
         if (mAnimations.containsKey(transition)) {
             throw new IllegalStateException("Got a duplicate startAnimation call for "
                     + transition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index f432049..bda884c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -107,6 +107,7 @@
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Checking filter %s",
                         mFilters.get(i));
                 if (mFilters.get(i).first.matches(info)) {
+                    Slog.d(TAG, "Found filter" + mFilters.get(i));
                     pendingRemote = mFilters.get(i).second;
                     // Add to requested list so that it can be found for merge requests.
                     mRequestedRemotes.put(transition, pendingRemote);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index ba73d55..1a70f76 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -25,6 +25,7 @@
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
 
 import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
@@ -210,15 +211,15 @@
     }
 
     @Test
-    public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets() {
+    public void testDragAppOverFullscreenApp_expectSplitScreenTargets() {
         setRunningTask(mFullscreenAppTask);
         mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
         ArrayList<Target> targets = assertExactTargetTypes(
-                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
+                mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
         verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
+                eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
         reset(mSplitScreenStarter);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
@@ -227,15 +228,15 @@
     }
 
     @Test
-    public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets() {
+    public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenTargets() {
         setRunningTask(mFullscreenAppTask);
         mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
         ArrayList<Target> targets = assertExactTargetTypes(
-                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
+                mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
         verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
+                eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
         reset(mSplitScreenStarter);
 
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
@@ -244,65 +245,55 @@
     }
 
     @Test
-    public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets() {
-        setRunningTask(mNonResizeableFullscreenAppTask);
-        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
-        ArrayList<Target> targets = assertExactTargetTypes(
-                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
-
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
-    }
-
-    @Test
-    public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets() {
-        setRunningTask(mFullscreenAppTask);
-        mPolicy.start(mLandscapeDisplayLayout, mNonResizeableActivityClipData);
-        ArrayList<Target> targets = assertExactTargetTypes(
-                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
-
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
-        verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
-    }
-
-    @Test
-    public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() {
+    public void testDragAppOverSplitApp_expectSplitTargets_DropLeft() {
         setInSplitScreen(true);
         setRunningTask(mSplitPrimaryAppTask);
         mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
         ArrayList<Target> targets = assertExactTargetTypes(
-                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
+                mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_LEFT), mActivityClipData);
         verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
-        reset(mSplitScreenStarter);
+                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+    }
 
-        // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
+    @Test
+    public void testDragAppOverSplitApp_expectSplitTargets_DropRight() {
+        setInSplitScreen(true);
+        setRunningTask(mSplitPrimaryAppTask);
+        mPolicy.start(mLandscapeDisplayLayout, mActivityClipData);
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
+
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
         verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
     }
 
     @Test
-    public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets() {
+    public void testDragAppOverSplitAppPhone_expectVerticalSplitTargets_DropTop() {
         setInSplitScreen(true);
         setRunningTask(mSplitPrimaryAppTask);
         mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
         ArrayList<Target> targets = assertExactTargetTypes(
-                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
+                mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
 
-        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_TOP), mActivityClipData);
         verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_UNDEFINED), any());
-        reset(mSplitScreenStarter);
+                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_TOP_OR_LEFT), any());
+    }
 
-        // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
+    @Test
+    public void testDragAppOverSplitAppPhone_expectVerticalSplitTargets_DropBottom() {
+        setInSplitScreen(true);
+        setRunningTask(mSplitPrimaryAppTask);
+        mPolicy.start(mPortraitDisplayLayout, mActivityClipData);
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
+
         mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
         verify(mSplitScreenStarter).startIntent(any(), any(),
-                eq(STAGE_TYPE_SIDE), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
+                eq(STAGE_TYPE_UNDEFINED), eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), any());
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 284f384..d536adb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -214,6 +214,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
         taskInfo.topActivityInfo = info;
         taskInfo.taskId = taskId;
+        windowInfo.targetActivityInfo = info;
         windowInfo.taskInfo = taskInfo;
         return windowInfo;
     }
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index c4cdb7d..54367b8 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -44,8 +44,6 @@
 #include <utils/StrongPointer.h>
 #include <utils/Timers.h>
 
-#include <pthread.h>
-
 #include <algorithm>
 #include <atomic>
 #include <vector>
@@ -60,10 +58,6 @@
 struct {
     jclass clazz;
     jmethodID invokePictureCapturedCallback;
-    jmethodID createHintSession;
-    jmethodID updateTargetWorkDuration;
-    jmethodID reportActualWorkDuration;
-    jmethodID closeHintSession;
 } gHardwareRenderer;
 
 struct {
@@ -90,14 +84,6 @@
     return env;
 }
 
-static bool hasExceptionAndClear(JNIEnv* env) {
-    if (GraphicsJNI::hasException(env)) {
-        env->ExceptionClear();
-        return true;
-    }
-    return false;
-}
-
 typedef ANativeWindow* (*ANW_fromSurface)(JNIEnv* env, jobject surface);
 ANW_fromSurface fromSurface;
 
@@ -147,67 +133,6 @@
     }
 };
 
-class HintSessionWrapper : public LightRefBase<HintSessionWrapper> {
-public:
-    static sp<HintSessionWrapper> create(JNIEnv* env, RenderProxy* proxy) {
-        if (!Properties::useHintManager) return nullptr;
-
-        // Include UI thread (self), render thread, and thread pool.
-        std::vector<int> tids = CommonPool::getThreadIds();
-        tids.push_back(proxy->getRenderThreadTid());
-        tids.push_back(pthread_gettid_np(pthread_self()));
-
-        jintArray tidsArray = env->NewIntArray(tids.size());
-        if (hasExceptionAndClear(env)) return nullptr;
-        env->SetIntArrayRegion(tidsArray, 0, tids.size(), reinterpret_cast<jint*>(tids.data()));
-        if (hasExceptionAndClear(env)) return nullptr;
-        jobject session = env->CallStaticObjectMethod(
-                gHardwareRenderer.clazz, gHardwareRenderer.createHintSession, tidsArray);
-        if (hasExceptionAndClear(env) || !session) return nullptr;
-        return new HintSessionWrapper(env, session);
-    }
-
-    ~HintSessionWrapper() {
-        if (!mSession) return;
-        JNIEnv* env = getenv(mVm);
-        env->CallStaticVoidMethod(gHardwareRenderer.clazz, gHardwareRenderer.closeHintSession,
-                                  mSession);
-        hasExceptionAndClear(env);
-        env->DeleteGlobalRef(mSession);
-        mSession = nullptr;
-    }
-
-    void updateTargetWorkDuration(long targetDurationNanos) {
-        if (!mSession) return;
-        JNIEnv* env = getenv(mVm);
-        env->CallStaticVoidMethod(gHardwareRenderer.clazz,
-                                  gHardwareRenderer.updateTargetWorkDuration, mSession,
-                                  static_cast<jlong>(targetDurationNanos));
-        hasExceptionAndClear(env);
-    }
-
-    void reportActualWorkDuration(long actualDurationNanos) {
-        if (!mSession) return;
-        JNIEnv* env = getenv(mVm);
-        env->CallStaticVoidMethod(gHardwareRenderer.clazz,
-                                  gHardwareRenderer.reportActualWorkDuration, mSession,
-                                  static_cast<jlong>(actualDurationNanos));
-        hasExceptionAndClear(env);
-    }
-
-private:
-    HintSessionWrapper(JNIEnv* env, jobject jobject) {
-        env->GetJavaVM(&mVm);
-        if (jobject) {
-            mSession = env->NewGlobalRef(jobject);
-            LOG_ALWAYS_FATAL_IF(!mSession, "Failed to make global ref");
-        }
-    }
-
-    JavaVM* mVm = nullptr;
-    jobject mSession = nullptr;
-};
-
 static void android_view_ThreadedRenderer_rotateProcessStatsBuffer(JNIEnv* env, jobject clazz) {
     RenderProxy::rotateProcessStatsBuffer();
 }
@@ -235,12 +160,6 @@
     RootRenderNode* rootRenderNode = reinterpret_cast<RootRenderNode*>(rootRenderNodePtr);
     ContextFactoryImpl factory(rootRenderNode);
     RenderProxy* proxy = new RenderProxy(translucent, rootRenderNode, &factory);
-    sp<HintSessionWrapper> wrapper = HintSessionWrapper::create(env, proxy);
-    if (wrapper) {
-        proxy->setHintSessionCallbacks(
-                [wrapper](int64_t nanos) { wrapper->updateTargetWorkDuration(nanos); },
-                [wrapper](int64_t nanos) { wrapper->reportActualWorkDuration(nanos); });
-    }
     return (jlong) proxy;
 }
 
@@ -1059,18 +978,6 @@
     gHardwareRenderer.invokePictureCapturedCallback = GetStaticMethodIDOrDie(env, hardwareRenderer,
             "invokePictureCapturedCallback",
             "(JLandroid/graphics/HardwareRenderer$PictureCapturedCallback;)V");
-    gHardwareRenderer.createHintSession =
-            GetStaticMethodIDOrDie(env, hardwareRenderer, "createHintSession",
-                                   "([I)Landroid/os/PerformanceHintManager$Session;");
-    gHardwareRenderer.updateTargetWorkDuration =
-            GetStaticMethodIDOrDie(env, hardwareRenderer, "updateTargetWorkDuration",
-                                   "(Landroid/os/PerformanceHintManager$Session;J)V");
-    gHardwareRenderer.reportActualWorkDuration =
-            GetStaticMethodIDOrDie(env, hardwareRenderer, "reportActualWorkDuration",
-                                   "(Landroid/os/PerformanceHintManager$Session;J)V");
-    gHardwareRenderer.closeHintSession =
-            GetStaticMethodIDOrDie(env, hardwareRenderer, "closeHintSession",
-                                   "(Landroid/os/PerformanceHintManager$Session;)V");
 
     jclass aSurfaceTransactionCallbackClass =
             FindClassOrDie(env, "android/graphics/HardwareRenderer$ASurfaceTransactionCallback");
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index db29e34..e7081df 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -16,6 +16,7 @@
 
 #include "DrawFrameTask.h"
 
+#include <dlfcn.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 #include <algorithm>
@@ -26,11 +27,63 @@
 #include "../RenderNode.h"
 #include "CanvasContext.h"
 #include "RenderThread.h"
+#include "thread/CommonPool.h"
 
 namespace android {
 namespace uirenderer {
 namespace renderthread {
 
+namespace {
+
+typedef APerformanceHintManager* (*APH_getManager)();
+typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
+                                                      size_t, int64_t);
+typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
+typedef void (*APH_closeSession)(APerformanceHintSession* session);
+
+bool gAPerformanceHintBindingInitialized = false;
+APH_getManager gAPH_getManagerFn = nullptr;
+APH_createSession gAPH_createSessionFn = nullptr;
+APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
+APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
+APH_closeSession gAPH_closeSessionFn = nullptr;
+
+void ensureAPerformanceHintBindingInitialized() {
+    if (gAPerformanceHintBindingInitialized) return;
+
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
+
+    gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
+    LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_getManager!");
+
+    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_createSession!");
+
+    gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
+            handle_, "APerformanceHint_updateTargetWorkDuration");
+    LOG_ALWAYS_FATAL_IF(
+            gAPH_updateTargetWorkDurationFn == nullptr,
+            "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
+
+    gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
+            handle_, "APerformanceHint_reportActualWorkDuration");
+    LOG_ALWAYS_FATAL_IF(
+            gAPH_reportActualWorkDurationFn == nullptr,
+            "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
+
+    gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_closeSession!");
+
+    gAPerformanceHintBindingInitialized = true;
+}
+
+}  // namespace
+
 DrawFrameTask::DrawFrameTask()
         : mRenderThread(nullptr)
         , mContext(nullptr)
@@ -39,17 +92,13 @@
 
 DrawFrameTask::~DrawFrameTask() {}
 
-void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context,
-                               RenderNode* targetNode) {
+void DrawFrameTask::setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
+                               int32_t uiThreadId, int32_t renderThreadId) {
     mRenderThread = thread;
     mContext = context;
     mTargetNode = targetNode;
-}
-
-void DrawFrameTask::setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
-                                            std::function<void(int64_t)> reportActualWorkDuration) {
-    mUpdateTargetWorkDuration = std::move(updateTargetWorkDuration);
-    mReportActualWorkDuration = std::move(reportActualWorkDuration);
+    mUiThreadId = uiThreadId;
+    mRenderThreadId = renderThreadId;
 }
 
 void DrawFrameTask::pushLayerUpdate(DeferredLayerUpdater* layer) {
@@ -144,27 +193,25 @@
         unblockUiThread();
     }
 
-    // These member callbacks are effectively const as they are set once during init, so it's safe
-    // to use these directly instead of making local copies.
-    if (mUpdateTargetWorkDuration && mReportActualWorkDuration) {
-        constexpr int64_t kSanityCheckLowerBound = 100000;       // 0.1ms
-        constexpr int64_t kSanityCheckUpperBound = 10000000000;  // 10s
-        int64_t targetWorkDuration = frameDeadline - intendedVsync;
-        targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
-        if (targetWorkDuration > kSanityCheckLowerBound &&
-            targetWorkDuration < kSanityCheckUpperBound &&
-            targetWorkDuration != mLastTargetWorkDuration) {
-            mLastTargetWorkDuration = targetWorkDuration;
-            mUpdateTargetWorkDuration(targetWorkDuration);
-        }
-        int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
-        int64_t actualDuration = frameDuration -
-                                 (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
-                                 dequeueBufferDuration;
-        if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
-            mReportActualWorkDuration(actualDuration);
-        }
+    if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
+    constexpr int64_t kSanityCheckLowerBound = 100000;       // 0.1ms
+    constexpr int64_t kSanityCheckUpperBound = 10000000000;  // 10s
+    int64_t targetWorkDuration = frameDeadline - intendedVsync;
+    targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
+    if (targetWorkDuration > kSanityCheckLowerBound &&
+        targetWorkDuration < kSanityCheckUpperBound &&
+        targetWorkDuration != mLastTargetWorkDuration) {
+        mLastTargetWorkDuration = targetWorkDuration;
+        mHintSessionWrapper->updateTargetWorkDuration(targetWorkDuration);
     }
+    int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+    int64_t actualDuration = frameDuration -
+                             (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
+                             dequeueBufferDuration;
+    if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
+        mHintSessionWrapper->reportActualWorkDuration(actualDuration);
+    }
+
     mLastDequeueBufferDuration = dequeueBufferDuration;
 }
 
@@ -216,6 +263,44 @@
     mSignal.signal();
 }
 
+DrawFrameTask::HintSessionWrapper::HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId) {
+    if (!Properties::useHintManager) return;
+    if (uiThreadId < 0 || renderThreadId < 0) return;
+
+    ensureAPerformanceHintBindingInitialized();
+
+    APerformanceHintManager* manager = gAPH_getManagerFn();
+    if (!manager) return;
+
+    std::vector<int32_t> tids = CommonPool::getThreadIds();
+    tids.push_back(uiThreadId);
+    tids.push_back(renderThreadId);
+
+    // DrawFrameTask code will always set a target duration before reporting actual durations.
+    // So this is just a placeholder value that's never used.
+    int64_t dummyTargetDurationNanos = 16666667;
+    mHintSession =
+            gAPH_createSessionFn(manager, tids.data(), tids.size(), dummyTargetDurationNanos);
+}
+
+DrawFrameTask::HintSessionWrapper::~HintSessionWrapper() {
+    if (mHintSession) {
+        gAPH_closeSessionFn(mHintSession);
+    }
+}
+
+void DrawFrameTask::HintSessionWrapper::updateTargetWorkDuration(long targetDurationNanos) {
+    if (mHintSession) {
+        gAPH_updateTargetWorkDurationFn(mHintSession, targetDurationNanos);
+    }
+}
+
+void DrawFrameTask::HintSessionWrapper::reportActualWorkDuration(long actualDurationNanos) {
+    if (mHintSession) {
+        gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
+    }
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 2455ea8..6a61a2b 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -16,8 +16,10 @@
 #ifndef DRAWFRAMETASK_H
 #define DRAWFRAMETASK_H
 
+#include <optional>
 #include <vector>
 
+#include <performance_hint_private.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
 #include <utils/StrongPointer.h>
@@ -60,9 +62,8 @@
     DrawFrameTask();
     virtual ~DrawFrameTask();
 
-    void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode);
-    void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
-                                 std::function<void(int64_t)> reportActualWorkDuration);
+    void setContext(RenderThread* thread, CanvasContext* context, RenderNode* targetNode,
+                    int32_t uiThreadId, int32_t renderThreadId);
     void setContentDrawBounds(int left, int top, int right, int bottom) {
         mContentDrawBounds.set(left, top, right, bottom);
     }
@@ -85,6 +86,18 @@
     }
 
 private:
+    class HintSessionWrapper {
+    public:
+        HintSessionWrapper(int32_t uiThreadId, int32_t renderThreadId);
+        ~HintSessionWrapper();
+
+        void updateTargetWorkDuration(long targetDurationNanos);
+        void reportActualWorkDuration(long actualDurationNanos);
+
+    private:
+        APerformanceHintSession* mHintSession = nullptr;
+    };
+
     void postAndWait();
     bool syncFrameState(TreeInfo& info);
     void unblockUiThread();
@@ -95,6 +108,8 @@
     RenderThread* mRenderThread;
     CanvasContext* mContext;
     RenderNode* mTargetNode = nullptr;
+    int32_t mUiThreadId = -1;
+    int32_t mRenderThreadId = -1;
     Rect mContentDrawBounds;
 
     /*********************************************
@@ -112,8 +127,7 @@
 
     nsecs_t mLastDequeueBufferDuration = 0;
     nsecs_t mLastTargetWorkDuration = 0;
-    std::function<void(int64_t)> mUpdateTargetWorkDuration;
-    std::function<void(int64_t)> mReportActualWorkDuration;
+    std::optional<HintSessionWrapper> mHintSessionWrapper;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index a77b5b5..c485ce2 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -29,6 +29,8 @@
 #include "utils/Macros.h"
 #include "utils/TimeUtils.h"
 
+#include <pthread.h>
+
 namespace android {
 namespace uirenderer {
 namespace renderthread {
@@ -39,7 +41,8 @@
     mContext = mRenderThread.queue().runSync([&]() -> CanvasContext* {
         return CanvasContext::create(mRenderThread, translucent, rootRenderNode, contextFactory);
     });
-    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode);
+    mDrawFrameTask.setContext(&mRenderThread, mContext, rootRenderNode,
+                              pthread_gettid_np(pthread_self()), getRenderThreadTid());
 }
 
 RenderProxy::~RenderProxy() {
@@ -48,7 +51,7 @@
 
 void RenderProxy::destroyContext() {
     if (mContext) {
-        mDrawFrameTask.setContext(nullptr, nullptr, nullptr);
+        mDrawFrameTask.setContext(nullptr, nullptr, nullptr, -1, -1);
         // This is also a fence as we need to be certain that there are no
         // outstanding mDrawFrame tasks posted before it is destroyed
         mRenderThread.queue().runSync([this]() { delete mContext; });
@@ -76,12 +79,6 @@
     mRenderThread.queue().runSync([this, name]() { mContext->setName(std::string(name)); });
 }
 
-void RenderProxy::setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
-                                          std::function<void(int64_t)> reportActualWorkDuration) {
-    mDrawFrameTask.setHintSessionCallbacks(std::move(updateTargetWorkDuration),
-                                           std::move(reportActualWorkDuration));
-}
-
 void RenderProxy::setSurface(ANativeWindow* window, bool enableTimeout) {
     if (window) { ANativeWindow_acquire(window); }
     mRenderThread.queue().post([this, win = window, enableTimeout]() mutable {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 1b0f22e..2b5405c 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -71,8 +71,6 @@
     void setSwapBehavior(SwapBehavior swapBehavior);
     bool loadSystemProperties();
     void setName(const char* name);
-    void setHintSessionCallbacks(std::function<void(int64_t)> updateTargetWorkDuration,
-                                 std::function<void(int64_t)> reportActualWorkDuration);
 
     void setSurface(ANativeWindow* window, bool enableTimeout = true);
     void setSurfaceControl(ASurfaceControl* surfaceControl);
diff --git a/media/java/android/media/metrics/PlaybackErrorEvent.java b/media/java/android/media/metrics/PlaybackErrorEvent.java
index 184b359..4e3b426 100644
--- a/media/java/android/media/metrics/PlaybackErrorEvent.java
+++ b/media/java/android/media/metrics/PlaybackErrorEvent.java
@@ -317,7 +317,7 @@
      */
     public static final class Builder {
         private @Nullable Exception mException;
-        private int mErrorCode;
+        private int mErrorCode = ERROR_UNKNOWN;
         private int mSubErrorCode;
         private long mTimeSinceCreatedMillis = -1;
         private Bundle mMetricsBundle = new Bundle();
diff --git a/media/java/android/media/metrics/PlaybackMetrics.java b/media/java/android/media/metrics/PlaybackMetrics.java
index bbcc484..e71ee20 100644
--- a/media/java/android/media/metrics/PlaybackMetrics.java
+++ b/media/java/android/media/metrics/PlaybackMetrics.java
@@ -502,9 +502,9 @@
         private long mMediaDurationMillis = -1;
         private int mStreamSource = STREAM_SOURCE_UNKNOWN;
         private int mStreamType = STREAM_TYPE_UNKNOWN;
-        private int mPlaybackType = PLAYBACK_TYPE_OTHER;
+        private int mPlaybackType = PLAYBACK_TYPE_UNKNOWN;
         private int mDrmType = DRM_TYPE_NONE;
-        private int mContentType = CONTENT_TYPE_OTHER;
+        private int mContentType = CONTENT_TYPE_UNKNOWN;
         private @Nullable String mPlayerName;
         private @Nullable String mPlayerVersion;
         private @NonNull List<Long> mExperimentIds = new ArrayList<>();
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3ee2c18..32b7a07 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -57,6 +57,7 @@
         "net.c",
         "obb.cpp",
         "permission_manager.cpp",
+        "performance_hint.cpp",
         "sensor.cpp",
         "sharedmem.cpp",
         "storage_manager.cpp",
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index de6db1a..f33e118 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -312,6 +312,13 @@
 
 LIBANDROID_PLATFORM {
   global:
+    APerformanceHint_getManager;
+    APerformanceHint_createSession;
+    APerformanceHint_getPreferredUpdateRateNanos;
+    APerformanceHint_updateTargetWorkDuration;
+    APerformanceHint_reportActualWorkDuration;
+    APerformanceHint_closeSession;
+    APerformanceHint_setIHintManagerForTesting;
     extern "C++" {
         ASurfaceControl_registerSurfaceStatsListener*;
         ASurfaceControl_unregisterSurfaceStatsListener*;
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
new file mode 100644
index 0000000..95a2da9
--- /dev/null
+++ b/native/android/performance_hint.cpp
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "perf_hint"
+
+#include <utility>
+#include <vector>
+
+#include <android/os/IHintManager.h>
+#include <android/os/IHintSession.h>
+#include <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <performance_hint_private.h>
+#include <utils/SystemClock.h>
+
+using namespace android;
+using namespace android::os;
+
+struct APerformanceHintSession;
+
+struct APerformanceHintManager {
+public:
+    static APerformanceHintManager* getInstance();
+    APerformanceHintManager(sp<IHintManager> service, int64_t preferredRateNanos);
+    APerformanceHintManager() = delete;
+    ~APerformanceHintManager() = default;
+
+    APerformanceHintSession* createSession(const int32_t* threadIds, size_t size,
+                                           int64_t initialTargetWorkDurationNanos);
+    int64_t getPreferredRateNanos() const;
+
+private:
+    static APerformanceHintManager* create(sp<IHintManager> iHintManager);
+
+    sp<IHintManager> mHintManager;
+    const int64_t mPreferredRateNanos;
+};
+
+struct APerformanceHintSession {
+public:
+    APerformanceHintSession(sp<IHintSession> session, int64_t preferredRateNanos,
+                            int64_t targetDurationNanos);
+    APerformanceHintSession() = delete;
+    ~APerformanceHintSession();
+
+    int updateTargetWorkDuration(int64_t targetDurationNanos);
+    int reportActualWorkDuration(int64_t actualDurationNanos);
+
+private:
+    friend struct APerformanceHintManager;
+
+    sp<IHintSession> mHintSession;
+    // HAL preferred update rate
+    const int64_t mPreferredRateNanos;
+    // Target duration for choosing update rate
+    int64_t mTargetDurationNanos;
+    // Last update timestamp
+    int64_t mLastUpdateTimestamp;
+    // Cached samples
+    std::vector<int64_t> mActualDurationsNanos;
+    std::vector<int64_t> mTimestampsNanos;
+};
+
+static IHintManager* gIHintManagerForTesting = nullptr;
+static APerformanceHintManager* gHintManagerForTesting = nullptr;
+
+// ===================================== APerformanceHintManager implementation
+APerformanceHintManager::APerformanceHintManager(sp<IHintManager> manager,
+                                                 int64_t preferredRateNanos)
+      : mHintManager(std::move(manager)), mPreferredRateNanos(preferredRateNanos) {}
+
+APerformanceHintManager* APerformanceHintManager::getInstance() {
+    if (gHintManagerForTesting) return gHintManagerForTesting;
+    if (gIHintManagerForTesting) {
+        APerformanceHintManager* manager = create(gIHintManagerForTesting);
+        gIHintManagerForTesting = nullptr;
+        return manager;
+    }
+    static APerformanceHintManager* instance = create(nullptr);
+    return instance;
+}
+
+APerformanceHintManager* APerformanceHintManager::create(sp<IHintManager> manager) {
+    if (!manager) {
+        manager = interface_cast<IHintManager>(
+                defaultServiceManager()->checkService(String16("performance_hint")));
+    }
+    if (manager == nullptr) {
+        ALOGE("%s: PerformanceHint service is not ready ", __FUNCTION__);
+        return nullptr;
+    }
+    int64_t preferredRateNanos = -1L;
+    binder::Status ret = manager->getHintSessionPreferredRate(&preferredRateNanos);
+    if (!ret.isOk()) {
+        ALOGE("%s: PerformanceHint cannot get preferred rate. %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return nullptr;
+    }
+    if (preferredRateNanos <= 0) {
+        ALOGE("%s: PerformanceHint invalid preferred rate.", __FUNCTION__);
+        return nullptr;
+    }
+    return new APerformanceHintManager(std::move(manager), preferredRateNanos);
+}
+
+APerformanceHintSession* APerformanceHintManager::createSession(
+        const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos) {
+    sp<IBinder> token = sp<BBinder>::make();
+    std::vector<int32_t> tids(threadIds, threadIds + size);
+    sp<IHintSession> session;
+    binder::Status ret =
+            mHintManager->createHintSession(token, tids, initialTargetWorkDurationNanos, &session);
+    if (!ret.isOk() || !session) {
+        return nullptr;
+    }
+    return new APerformanceHintSession(std::move(session), mPreferredRateNanos,
+                                       initialTargetWorkDurationNanos);
+}
+
+int64_t APerformanceHintManager::getPreferredRateNanos() const {
+    return mPreferredRateNanos;
+}
+
+// ===================================== APerformanceHintSession implementation
+
+APerformanceHintSession::APerformanceHintSession(sp<IHintSession> session,
+                                                 int64_t preferredRateNanos,
+                                                 int64_t targetDurationNanos)
+      : mHintSession(std::move(session)),
+        mPreferredRateNanos(preferredRateNanos),
+        mTargetDurationNanos(targetDurationNanos),
+        mLastUpdateTimestamp(elapsedRealtimeNano()) {}
+
+APerformanceHintSession::~APerformanceHintSession() {
+    binder::Status ret = mHintSession->close();
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession close failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
+    }
+}
+
+int APerformanceHintSession::updateTargetWorkDuration(int64_t targetDurationNanos) {
+    if (targetDurationNanos <= 0) {
+        ALOGE("%s: targetDurationNanos must be positive", __FUNCTION__);
+        return EINVAL;
+    }
+    binder::Status ret = mHintSession->updateTargetWorkDuration(targetDurationNanos);
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSessionn updateTargetWorkDuration failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    mTargetDurationNanos = targetDurationNanos;
+    /**
+     * Most of the workload is target_duration dependent, so now clear the cached samples
+     * as they are most likely obsolete.
+     */
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
+    mLastUpdateTimestamp = elapsedRealtimeNano();
+    return 0;
+}
+
+int APerformanceHintSession::reportActualWorkDuration(int64_t actualDurationNanos) {
+    if (actualDurationNanos <= 0) {
+        ALOGE("%s: actualDurationNanos must be positive", __FUNCTION__);
+        return EINVAL;
+    }
+    int64_t now = elapsedRealtimeNano();
+    mActualDurationsNanos.push_back(actualDurationNanos);
+    mTimestampsNanos.push_back(now);
+
+    /**
+     * Use current sample to determine the rate limit. We can pick a shorter rate limit
+     * if any sample underperformed, however, it could be the lower level system is slow
+     * to react. So here we explicitly choose the rate limit with the latest sample.
+     */
+    int64_t rateLimit = actualDurationNanos > mTargetDurationNanos ? mPreferredRateNanos
+                                                                   : 10 * mPreferredRateNanos;
+    if (now - mLastUpdateTimestamp <= rateLimit) return 0;
+
+    binder::Status ret =
+            mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
+    mActualDurationsNanos.clear();
+    mTimestampsNanos.clear();
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    mLastUpdateTimestamp = now;
+    return 0;
+}
+
+// ===================================== C API
+APerformanceHintManager* APerformanceHint_getManager() {
+    return APerformanceHintManager::getInstance();
+}
+
+APerformanceHintSession* APerformanceHint_createSession(APerformanceHintManager* manager,
+                                                        const int32_t* threadIds, size_t size,
+                                                        int64_t initialTargetWorkDurationNanos) {
+    return manager->createSession(threadIds, size, initialTargetWorkDurationNanos);
+}
+
+int64_t APerformanceHint_getPreferredUpdateRateNanos(APerformanceHintManager* manager) {
+    return manager->getPreferredRateNanos();
+}
+
+int APerformanceHint_updateTargetWorkDuration(APerformanceHintSession* session,
+                                              int64_t targetDurationNanos) {
+    return session->updateTargetWorkDuration(targetDurationNanos);
+}
+
+int APerformanceHint_reportActualWorkDuration(APerformanceHintSession* session,
+                                              int64_t actualDurationNanos) {
+    return session->reportActualWorkDuration(actualDurationNanos);
+}
+
+void APerformanceHint_closeSession(APerformanceHintSession* session) {
+    delete session;
+}
+
+void APerformanceHint_setIHintManagerForTesting(void* iManager) {
+    delete gHintManagerForTesting;
+    gHintManagerForTesting = nullptr;
+    gIHintManagerForTesting = static_cast<IHintManager*>(iManager);
+}
diff --git a/native/android/tests/performance_hint/Android.bp b/native/android/tests/performance_hint/Android.bp
new file mode 100644
index 0000000..fdc1bc6
--- /dev/null
+++ b/native/android/tests/performance_hint/Android.bp
@@ -0,0 +1,65 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_test {
+    name: "PerformanceHintNativeTestCases",
+
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["PerformanceHintNativeTest.cpp"],
+
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libbinder",
+        "libpowermanager",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libgmock",
+        "libgtest",
+    ],
+    stl: "c++_shared",
+
+    test_suites: [
+        "device-tests",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+    header_libs: [
+        "libandroid_headers_private",
+    ],
+}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
new file mode 100644
index 0000000..284e9ee
--- /dev/null
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "PerformanceHintNativeTest"
+
+#include <android/os/IHintManager.h>
+#include <android/os/IHintSession.h>
+#include <binder/IBinder.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <performance_hint_private.h>
+#include <memory>
+#include <vector>
+
+using android::binder::Status;
+using android::os::IHintManager;
+using android::os::IHintSession;
+
+using namespace android;
+using namespace testing;
+
+class MockIHintManager : public IHintManager {
+public:
+    MOCK_METHOD(Status, createHintSession,
+                (const ::android::sp<::android::IBinder>& token, const ::std::vector<int32_t>& tids,
+                 int64_t durationNanos, ::android::sp<::android::os::IHintSession>* _aidl_return),
+                (override));
+    MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * _aidl_return), (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class MockIHintSession : public IHintSession {
+public:
+    MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t targetDurationNanos), (override));
+    MOCK_METHOD(Status, reportActualWorkDuration,
+                (const ::std::vector<int64_t>& actualDurationNanos,
+                 const ::std::vector<int64_t>& timeStampNanos),
+                (override));
+    MOCK_METHOD(Status, close, (), (override));
+    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+};
+
+class PerformanceHintTest : public Test {
+public:
+    void SetUp() override {
+        mMockIHintManager = new StrictMock<MockIHintManager>();
+        APerformanceHint_setIHintManagerForTesting(mMockIHintManager);
+    }
+
+    void TearDown() override {
+        mMockIHintManager = nullptr;
+        // Destroys MockIHintManager.
+        APerformanceHint_setIHintManagerForTesting(nullptr);
+    }
+
+    APerformanceHintManager* createManager() {
+        EXPECT_CALL(*mMockIHintManager, getHintSessionPreferredRate(_))
+                .Times(Exactly(1))
+                .WillRepeatedly(DoAll(SetArgPointee<0>(123L), Return(Status())));
+        return APerformanceHint_getManager();
+    }
+
+    StrictMock<MockIHintManager>* mMockIHintManager = nullptr;
+};
+
+TEST_F(PerformanceHintTest, TestGetPreferredUpdateRateNanos) {
+    APerformanceHintManager* manager = createManager();
+    int64_t preferredUpdateRateNanos = APerformanceHint_getPreferredUpdateRateNanos(manager);
+    EXPECT_EQ(123L, preferredUpdateRateNanos);
+}
+
+TEST_F(PerformanceHintTest, TestSession) {
+    APerformanceHintManager* manager = createManager();
+
+    std::vector<int32_t> tids;
+    tids.push_back(1);
+    tids.push_back(2);
+    int64_t targetDuration = 56789L;
+
+    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+    sp<IHintSession> session_sp(iSession);
+
+    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+    APerformanceHintSession* session =
+            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+    ASSERT_TRUE(session);
+
+    int64_t targetDurationNanos = 10;
+    EXPECT_CALL(*iSession, updateTargetWorkDuration(Eq(targetDurationNanos))).Times(Exactly(1));
+    int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
+    EXPECT_EQ(0, result);
+
+    usleep(2); // Sleep for longer than preferredUpdateRateNanos.
+    int64_t actualDurationNanos = 20;
+    std::vector<int64_t> actualDurations;
+    actualDurations.push_back(20);
+    EXPECT_CALL(*iSession, reportActualWorkDuration(Eq(actualDurations), _)).Times(Exactly(1));
+    result = APerformanceHint_reportActualWorkDuration(session, actualDurationNanos);
+    EXPECT_EQ(0, result);
+
+    result = APerformanceHint_updateTargetWorkDuration(session, -1L);
+    EXPECT_EQ(EINVAL, result);
+    result = APerformanceHint_reportActualWorkDuration(session, -1L);
+    EXPECT_EQ(EINVAL, result);
+
+    EXPECT_CALL(*iSession, close()).Times(Exactly(1));
+    APerformanceHint_closeSession(session);
+}
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 3d62af0..7f258eb 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -1,26 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.egg"
-    android:versionCode="1"
+    android:versionCode="12"
     android:versionName="1.0">
 
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
     <!-- used for cat notifications -->
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+
     <!-- used to save cat images -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <!-- controls -->
     <uses-permission android:name="android.permission.BIND_CONTROLS" />
 
     <application
         android:icon="@drawable/icon"
         android:label="@string/app_name">
-
-        <activity android:name=".quares.QuaresActivity"
+        <activity
+            android:name=".quares.QuaresActivity"
+            android:exported="true"
             android:icon="@drawable/q_icon"
             android:label="@string/q_egg_name"
-            android:exported="true"
             android:theme="@style/QuaresTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -29,9 +31,9 @@
         <activity
             android:name=".paint.PaintActivity"
             android:configChanges="orientation|keyboardHidden|screenSize|uiMode"
+            android:exported="true"
             android:icon="@drawable/p_icon"
             android:label="@string/p_egg_name"
-            android:exported="true"
             android:theme="@style/AppTheme">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -39,13 +41,15 @@
         </activity>
 
         <!-- Android N easter egg bits -->
-        <activity android:name=".neko.NekoLand"
-            android:theme="@android:style/Theme.Material.NoActionBar"
+        <activity
+            android:name=".neko.NekoLand"
             android:exported="true"
-            android:label="@string/app_name">
+            android:label="@string/app_name"
+            android:theme="@android:style/Theme.Material.NoActionBar">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
                 <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
@@ -54,25 +58,24 @@
         <service
             android:name=".neko.NekoService"
             android:enabled="true"
-            android:permission="android.permission.BIND_JOB_SERVICE"
-            android:exported="true" >
-        </service>
-
+            android:exported="true"
+            android:permission="android.permission.BIND_JOB_SERVICE" />
         <!-- Used to show over lock screen -->
-        <activity android:name=".neko.NekoLockedActivity"
+        <activity
+            android:name=".neko.NekoLockedActivity"
             android:excludeFromRecents="true"
             android:exported="true"
-            android:theme="@android:style/Theme.Material.Light.Dialog.NoActionBar"
-            android:showOnLockScreen="true" />
-
+            android:showOnLockScreen="true"
+            android:theme="@android:style/Theme.Material.Light.Dialog.NoActionBar" />
         <!-- Used to enable easter egg -->
-        <activity android:name=".neko.NekoActivationActivity"
+        <activity
+            android:name=".ComponentActivationActivity"
             android:excludeFromRecents="true"
             android:exported="true"
-            android:theme="@android:style/Theme.NoDisplay"
-            >
+            android:theme="@android:style/Theme.NoDisplay">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
+                <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="com.android.internal.category.PLATLOGO" />
             </intent-filter>
@@ -81,37 +84,65 @@
         <!-- The quick settings tile, disabled by default -->
         <service
             android:name=".neko.NekoTile"
-            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
-            android:icon="@drawable/stat_icon"
             android:enabled="false"
             android:exported="true"
-            android:label="@string/default_tile_name">
+            android:icon="@drawable/stat_icon"
+            android:label="@string/default_tile_name"
+            android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE" />
             </intent-filter>
         </service>
-
-        <service android:name=".neko.NekoControlsService"
-            android:permission="android.permission.BIND_CONTROLS"
-            android:label="@string/r_egg_name"
-            android:icon="@drawable/ic_fullcat_icon"
+        <service
+            android:name=".neko.NekoControlsService"
             android:enabled="false"
-            android:exported="true">
+            android:exported="true"
+            android:icon="@drawable/ic_fullcat_icon"
+            android:label="@string/r_egg_name"
+            android:permission="android.permission.BIND_CONTROLS">
             <intent-filter>
                 <action android:name="android.service.controls.ControlsProviderService" />
             </intent-filter>
-        </service>
-
-        <!-- FileProvider for sending pictures -->
+        </service> <!-- FileProvider for sending pictures -->
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="com.android.egg.fileprovider"
-            android:grantUriPermissions="true"
-            android:exported="false">
+            android:exported="false"
+            android:grantUriPermissions="true">
             <meta-data
                 android:name="android.support.FILE_PROVIDER_PATHS"
                 android:resource="@xml/filepaths" />
         </provider>
+
+        <!-- Android S easter egg bits -->
+
+        <!-- List of all system theme colors on the device. -->
+        <activity
+            android:name=".widget.PaintChipsActivity"
+            android:theme="@android:style/Theme.Material.Wallpaper.NoTitleBar"
+            android:configChanges="orientation|keyboardHidden|screenSize|uiMode"
+            android:label="@string/s_egg_name"
+            android:enabled="false"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <!-- Homescreen widget also showing paint chips (may be affected by the exact position in
+             the workspace) -->
+        <receiver
+            android:name=".widget.PaintChipsWidget"
+            android:label="@string/s_egg_name"
+            android:enabled="false">
+            <intent-filter>
+                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.appwidget.provider"
+                android:resource="@xml/paint_chips_widget_info" />
+        </receiver>
     </application>
 
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/packages/EasterEgg/build.gradle b/packages/EasterEgg/build.gradle
index 20b4698..0565369 100644
--- a/packages/EasterEgg/build.gradle
+++ b/packages/EasterEgg/build.gradle
@@ -7,8 +7,8 @@
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.0.0'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath 'com.android.tools.build:gradle:7.0.0-alpha08'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
     }
 }
 
@@ -62,6 +62,9 @@
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
         }
     }
+    buildFeatures {
+        viewBinding true
+    }
 
 
 }
@@ -74,6 +77,7 @@
     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
     implementation "androidx.recyclerview:recyclerview:${ANDROID_X_VERSION}"
     implementation "androidx.dynamicanimation:dynamicanimation:${ANDROID_X_VERSION}"
+    implementation 'com.google.android.material:material:1.3.0'
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'androidx.test.ext:junit:1.1.1'
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
diff --git a/packages/EasterEgg/gradle.properties b/packages/EasterEgg/gradle.properties
index e8e6450..0b5a736 100644
--- a/packages/EasterEgg/gradle.properties
+++ b/packages/EasterEgg/gradle.properties
@@ -19,5 +19,5 @@
 kotlin.code.style=official
 
 ANDROID_X_VERSION=1+
-COMPILE_SDK=android-30
+COMPILE_SDK=android-S
 BUILD_TOOLS_VERSION=28.0.3
diff --git a/packages/EasterEgg/res/drawable/android_s.xml b/packages/EasterEgg/res/drawable/android_s.xml
new file mode 100644
index 0000000..9cecab1
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android_s.xml
@@ -0,0 +1,23 @@
+<vector android:height="108dp" android:viewportHeight="48"
+    android:viewportWidth="48" android:width="108dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <group>
+        <clip-path android:pathData="M17,14h14v20h-14z"/>
+        <path android:fillColor="#00000000"
+            android:pathData="M18,21C18,21.7956 18.3161,22.5587 18.8787,23.1213C19.4413,23.6839 20.2044,24 21,24H27C27.7956,24 28.5587,24.3161 29.1213,24.8787C29.6839,25.4413 30,26.2044 30,27"
+            android:strokeColor="#ffffff" android:strokeWidth="2"/>
+        <path android:fillColor="#ffffff" android:pathData="M22,21C22.5523,21 23,20.5523 23,20C23,19.4477 22.5523,19 22,19C21.4477,19 21,19.4477 21,20C21,20.5523 21.4477,21 22,21Z"/>
+        <path android:fillColor="#ffffff" android:pathData="M26,21C26.5523,21 27,20.5523 27,20C27,19.4477 26.5523,19 26,19C25.4477,19 25,19.4477 25,20C25,20.5523 25.4477,21 26,21Z"/>
+        <path android:fillColor="#00000000"
+            android:pathData="M19.5,16.5L18,15"
+            android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeWidth="2"/>
+        <path android:fillColor="#00000000"
+            android:pathData="M28.5,16.5L30,15"
+            android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeWidth="2"/>
+        <path android:fillColor="#00000000"
+            android:pathData="M29.92,20C29.8637,19.6605 29.7801,19.3261 29.67,19C29.205,17.6561 28.2777,16.5211 27.0536,15.7973C25.8294,15.0735 24.388,14.8081 22.9864,15.0483C21.5847,15.2885 20.314,16.0188 19.4007,17.1088C18.4874,18.1989 17.991,19.5779 18,21"
+            android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeWidth="2"/>
+        <path android:fillColor="#00000000"
+            android:pathData="M18.08,28C18.1363,28.3395 18.2199,28.6739 18.33,29C18.795,30.3439 19.7223,31.4789 20.9464,32.2027C22.1705,32.9265 23.612,33.1919 25.0136,32.9517C26.4153,32.7115 27.686,31.9812 28.5993,30.8912C29.5126,29.8011 30.009,28.4221 30,27"
+            android:strokeColor="#ffffff" android:strokeLineCap="round" android:strokeWidth="2"/>
+    </group>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/icon.xml b/packages/EasterEgg/res/drawable/icon.xml
index 7f8d4fa..7054962 100644
--- a/packages/EasterEgg/res/drawable/icon.xml
+++ b/packages/EasterEgg/res/drawable/icon.xml
@@ -15,5 +15,5 @@
 -->
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@drawable/icon_bg"/>
-    <foreground android:drawable="@drawable/android_11_dial"/>
+    <foreground android:drawable="@drawable/android_s"/>
 </adaptive-icon>
diff --git a/packages/EasterEgg/res/drawable/icon_bg.xml b/packages/EasterEgg/res/drawable/icon_bg.xml
index 31b2a7f..d08e160 100644
--- a/packages/EasterEgg/res/drawable/icon_bg.xml
+++ b/packages/EasterEgg/res/drawable/icon_bg.xml
@@ -14,5 +14,5 @@
     limitations under the License.
 -->
 <color xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#073042" />
+    android:color="@android:color/system_accent2_500" />
 
diff --git a/packages/EasterEgg/res/drawable/roundrect.xml b/packages/EasterEgg/res/drawable/roundrect.xml
new file mode 100644
index 0000000..070adad
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/roundrect.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#FF000000" />
+    <corners
+        android:radius="12dp" />
+</shape>
\ No newline at end of file
diff --git a/packages/EasterEgg/res/layout/paint_chip.xml b/packages/EasterEgg/res/layout/paint_chip.xml
new file mode 100644
index 0000000..d5745b9
--- /dev/null
+++ b/packages/EasterEgg/res/layout/paint_chip.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/chip"
+    android:layout_width="10dp" android:layout_height="10dp"
+    android:layout_gravity="fill" android:layout_columnWeight="1" android:layout_rowWeight="1"
+    android:text="A1-500"
+    android:backgroundTint="@android:color/system_accent1_500"
+    android:background="@drawable/roundrect"
+    android:gravity="center"
+    android:textColor="?android:attr/textColorPrimary"
+    android:fontFamily="?android:attr/textAppearanceLarge"
+    android:layout_margin="2dp"
+    android:singleLine="true"
+    />
\ No newline at end of file
diff --git a/packages/EasterEgg/res/layout/paint_chips_grid.xml b/packages/EasterEgg/res/layout/paint_chips_grid.xml
new file mode 100644
index 0000000..79f7013
--- /dev/null
+++ b/packages/EasterEgg/res/layout/paint_chips_grid.xml
@@ -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.
+-->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/paint_grid"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="0dp"
+    android:orientation="vertical"
+    android:alignmentMode="alignBounds"
+    android:rowOrderPreserved="false"
+    >
+</GridLayout>
diff --git a/packages/EasterEgg/res/layout/paint_chips_widget_preview.xml b/packages/EasterEgg/res/layout/paint_chips_widget_preview.xml
new file mode 100644
index 0000000..9893ec0
--- /dev/null
+++ b/packages/EasterEgg/res/layout/paint_chips_widget_preview.xml
@@ -0,0 +1,79 @@
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="300dp"
+    android:layout_height="50dp"
+    android:alignmentMode="alignBounds"
+    android:columnCount="5"
+    android:padding="0dp"
+    android:rowCount="1"
+    android:rowOrderPreserved="false">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_rowWeight="1"
+        android:layout_columnWeight="1"
+        android:layout_gravity="fill"
+        android:layout_margin="2dp"
+        android:background="@drawable/roundrect"
+        android:backgroundTint="@android:color/system_neutral1_500"
+        android:fontFamily="?android:attr/textAppearanceLarge"
+        android:gravity="center"
+        android:text="N1-500"
+        android:textColor="?android:attr/textColorPrimary" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_rowWeight="1"
+        android:layout_columnWeight="1"
+        android:layout_gravity="fill"
+        android:layout_margin="2dp"
+        android:background="@drawable/roundrect"
+        android:backgroundTint="@android:color/system_neutral2_500"
+        android:fontFamily="?android:attr/textAppearanceLarge"
+        android:gravity="center"
+        android:text="N2-500"
+        android:textColor="?android:attr/textColorPrimary" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_rowWeight="1"
+        android:layout_columnWeight="1"
+        android:layout_gravity="fill"
+        android:layout_margin="2dp"
+        android:background="@drawable/roundrect"
+        android:backgroundTint="@android:color/system_accent1_500"
+        android:fontFamily="?android:attr/textAppearanceLarge"
+        android:gravity="center"
+        android:text="A1-500"
+        android:textColor="?android:attr/textColorPrimary" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_rowWeight="1"
+        android:layout_columnWeight="1"
+        android:layout_gravity="fill"
+        android:layout_margin="2dp"
+        android:background="@drawable/roundrect"
+        android:backgroundTint="@android:color/system_accent2_500"
+        android:fontFamily="?android:attr/textAppearanceLarge"
+        android:gravity="center"
+        android:text="A2-500"
+        android:textColor="?android:attr/textColorPrimary" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_rowWeight="1"
+        android:layout_columnWeight="1"
+        android:layout_gravity="fill"
+        android:layout_margin="2dp"
+        android:background="@drawable/roundrect"
+        android:backgroundTint="@android:color/system_accent3_500"
+        android:fontFamily="?android:attr/textAppearanceLarge"
+        android:gravity="center"
+        android:text="A3-500"
+        android:textColor="?android:attr/textColorPrimary" />
+</GridLayout>
diff --git a/packages/EasterEgg/res/values-night/themes.xml b/packages/EasterEgg/res/values-night/themes.xml
new file mode 100644
index 0000000..83ec7a5
--- /dev/null
+++ b/packages/EasterEgg/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+<resources>
+
+    <style name="ThemeOverlay.EasterEgg.AppWidgetContainer" parent="">
+        <item name="appWidgetBackgroundColor">@color/light_blue_900</item>
+        <item name="appWidgetTextColor">@color/light_blue_200</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/EasterEgg/res/values/attrs.xml b/packages/EasterEgg/res/values/attrs.xml
new file mode 100644
index 0000000..97531a2
--- /dev/null
+++ b/packages/EasterEgg/res/values/attrs.xml
@@ -0,0 +1,6 @@
+<resources>
+    <declare-styleable name="AppWidgetAttrs">
+        <attr name="appWidgetBackgroundColor" format="color" />
+        <attr name="appWidgetTextColor" format="color" />
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/EasterEgg/res/values/colors.xml b/packages/EasterEgg/res/values/colors.xml
index 1a5388b..d79e83b 100644
--- a/packages/EasterEgg/res/values/colors.xml
+++ b/packages/EasterEgg/res/values/colors.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
     Copyright (C) 2018 The Android Open Source Project
 
     Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,4 +17,8 @@
     <color name="toolbar_bg_color">#FFDDDDDD</color>
     <color name="paper_color">#FFFFFFFF</color>
     <color name="paint_color">#FF000000</color>
+    <color name="light_blue_50">#FFE1F5FE</color>
+    <color name="light_blue_200">#FF81D4FA</color>
+    <color name="light_blue_600">#FF039BE5</color>
+    <color name="light_blue_900">#FF01579B</color>
 </resources>
\ No newline at end of file
diff --git a/packages/EasterEgg/res/values/dimens.xml b/packages/EasterEgg/res/values/dimens.xml
index e9dcebd..0de2c3c 100644
--- a/packages/EasterEgg/res/values/dimens.xml
+++ b/packages/EasterEgg/res/values/dimens.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
 Copyright (C) 2016 The Android Open Source Project
 
    Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,4 +15,10 @@
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
     <dimen name="neko_display_size">64dp</dimen>
+
+    <!--
+Refer to App Widget Documentation for margin information
+http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
+    -->
+    <dimen name="widget_margin">0dp</dimen>
 </resources>
diff --git a/packages/EasterEgg/res/values/strings.xml b/packages/EasterEgg/res/values/strings.xml
index 25f9421..743947a 100644
--- a/packages/EasterEgg/res/values/strings.xml
+++ b/packages/EasterEgg/res/values/strings.xml
@@ -14,7 +14,7 @@
     limitations under the License.
 -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <string name="app_name" translatable="false">Android R Easter Egg</string>
+    <string name="app_name" translatable="false">Android S Easter Egg</string>
 
     <!-- name of the Q easter egg, a nonogram-style icon puzzle -->
     <string name="q_egg_name" translatable="false">Icon Quiz</string>
@@ -23,4 +23,8 @@
     <string name="p_egg_name" translatable="false">PAINT.APK</string>
 
     <string name="r_egg_name" translatable="false">Cat Controls</string>
+
+    <!-- name of the S easter egg, a widget that displays the system color palette
+         in a manner similar to a set of paint samples from a hardware store -->
+    <string name="s_egg_name" translatable="false">Paint Chips</string>
 </resources>
diff --git a/packages/EasterEgg/res/values/themes.xml b/packages/EasterEgg/res/values/themes.xml
new file mode 100644
index 0000000..5b16304
--- /dev/null
+++ b/packages/EasterEgg/res/values/themes.xml
@@ -0,0 +1,7 @@
+<resources>
+
+    <style name="ThemeOverlay.EasterEgg.AppWidgetContainer" parent="">
+        <item name="appWidgetBackgroundColor">@color/light_blue_600</item>
+        <item name="appWidgetTextColor">@color/light_blue_50</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/EasterEgg/res/xml/paint_chips_widget_info.xml b/packages/EasterEgg/res/xml/paint_chips_widget_info.xml
new file mode 100644
index 0000000..7780a75
--- /dev/null
+++ b/packages/EasterEgg/res/xml/paint_chips_widget_info.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+    android:initialLayout="@layout/paint_chip"
+    android:previewLayout="@layout/paint_chips_widget_preview"
+    android:minWidth="50dp"
+    android:minHeight="50dp"
+    android:resizeMode="horizontal|vertical"
+    android:updatePeriodMillis="86400000"
+    android:widgetCategory="home_screen"></appwidget-provider>
\ No newline at end of file
diff --git a/packages/EasterEgg/src/com/android/egg/ComponentActivationActivity.java b/packages/EasterEgg/src/com/android/egg/ComponentActivationActivity.java
new file mode 100644
index 0000000..5820b5a
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/ComponentActivationActivity.java
@@ -0,0 +1,83 @@
+/*
+ * 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.egg;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.egg.neko.NekoControlsService;
+import com.android.egg.widget.PaintChipsActivity;
+import com.android.egg.widget.PaintChipsWidget;
+
+/**
+ * Launched from the PlatLogoActivity. Enables everything else in this easter egg.
+ */
+public class ComponentActivationActivity extends Activity {
+    private static final String TAG = "EasterEgg";
+
+    private static final String S_EGG_UNLOCK_SETTING = "egg_mode_s";
+
+    private void toastUp(String s) {
+        Toast toast = Toast.makeText(this, s, Toast.LENGTH_SHORT);
+        toast.show();
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        final PackageManager pm = getPackageManager();
+        final ComponentName[] cns = new ComponentName[] {
+                new ComponentName(this, NekoControlsService.class),
+                new ComponentName(this, PaintChipsActivity.class),
+                new ComponentName(this, PaintChipsWidget.class)
+        };
+        final long unlockValue = Settings.System.getLong(getContentResolver(),
+                S_EGG_UNLOCK_SETTING, 0);
+        for (ComponentName cn : cns) {
+            final boolean componentEnabled = pm.getComponentEnabledSetting(cn)
+                    == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+            if (unlockValue == 0) {
+                if (componentEnabled) {
+                    Log.v(TAG, "Disabling component: " + cn);
+                    pm.setComponentEnabledSetting(cn,
+                            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                            PackageManager.DONT_KILL_APP);
+                    //toastUp("\uD83D\uDEAB");
+                } else {
+                    Log.v(TAG, "Already disabled: " + cn);
+                }
+            } else {
+                if (!componentEnabled) {
+                    Log.v(TAG, "Enabling component: " + cn);
+                    pm.setComponentEnabledSetting(cn,
+                            PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                            PackageManager.DONT_KILL_APP);
+                    //toastUp("\uD83D\uDC31");
+                } else {
+                    Log.v(TAG, "Already enabled: " + cn);
+                }
+            }
+        }
+
+        finish();
+    }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoActivationActivity.java b/packages/EasterEgg/src/com/android/egg/neko/NekoActivationActivity.java
deleted file mode 100644
index df461c6..0000000
--- a/packages/EasterEgg/src/com/android/egg/neko/NekoActivationActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2016 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.egg.neko;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.provider.Settings;
-import android.util.Log;
-import android.widget.Toast;
-
-import com.android.internal.logging.MetricsLogger;
-
-public class NekoActivationActivity extends Activity {
-    private static final String R_EGG_UNLOCK_SETTING = "egg_mode_r";
-
-    private void toastUp(String s) {
-        Toast toast = Toast.makeText(this, s, Toast.LENGTH_SHORT);
-        toast.show();
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-
-        final PackageManager pm = getPackageManager();
-        final ComponentName cn = new ComponentName(this, NekoControlsService.class);
-        final boolean componentEnabled = pm.getComponentEnabledSetting(cn)
-                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-        if (Settings.System.getLong(getContentResolver(),
-                R_EGG_UNLOCK_SETTING, 0) == 0) {
-            if (componentEnabled) {
-                Log.v("Neko", "Disabling controls.");
-                pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                        PackageManager.DONT_KILL_APP);
-                MetricsLogger.histogram(this, "egg_neko_enable", 0);
-                toastUp("\uD83D\uDEAB");
-            } else {
-                Log.v("Neko", "Controls already disabled.");
-            }
-        } else {
-            if (!componentEnabled) {
-                Log.v("Neko", "Enabling controls.");
-                pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                        PackageManager.DONT_KILL_APP);
-                MetricsLogger.histogram(this, "egg_neko_enable", 1);
-                toastUp("\uD83D\uDC31");
-            } else {
-                Log.v("Neko", "Controls already enabled.");
-            }
-        }
-        finish();
-    }
-}
diff --git a/packages/EasterEgg/src/com/android/egg/widget/PaintChipsActivity.kt b/packages/EasterEgg/src/com/android/egg/widget/PaintChipsActivity.kt
new file mode 100644
index 0000000..8799aec
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/widget/PaintChipsActivity.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.egg.widget
+
+import android.app.Activity
+import android.content.res.Configuration
+import android.os.Bundle
+import android.widget.FrameLayout
+
+/**
+ * Activity to show off the current dynamic system theme in all its glory.
+ */
+class PaintChipsActivity : Activity() {
+    private lateinit var layout: FrameLayout
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        window.navigationBarColor = 0
+        window.statusBarColor = 0
+        actionBar?.hide()
+
+        layout = FrameLayout(this)
+        layout.setPadding(dp2px(8f), dp2px(8f), dp2px(8f), dp2px(8f))
+        rebuildGrid()
+
+        setContentView(layout)
+    }
+
+    fun dp2px(dp: Float): Int {
+        return (dp * resources.displayMetrics.density).toInt()
+    }
+
+    override fun onResume() {
+        super.onResume()
+
+        rebuildGrid()
+    }
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+
+        rebuildGrid()
+    }
+
+    private fun rebuildGrid() {
+        layout.removeAllViews()
+        val grid = buildFullWidget(this, ClickBehavior.SHARE)
+        val asView = grid.apply(this, layout)
+        layout.addView(asView, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
+            FrameLayout.LayoutParams.MATCH_PARENT))
+    }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/widget/PaintChipsWidget.kt b/packages/EasterEgg/src/com/android/egg/widget/PaintChipsWidget.kt
new file mode 100644
index 0000000..c15cabb
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/widget/PaintChipsWidget.kt
@@ -0,0 +1,293 @@
+/*
+ * 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.egg.widget
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.util.SizeF
+import android.widget.RemoteViews
+
+import com.android.egg.R
+
+/**
+ * A homescreen widget to explore the current dynamic system theme.
+ */
+class PaintChipsWidget : AppWidgetProvider() {
+    override fun onUpdate(
+        context: Context,
+        appWidgetManager: AppWidgetManager,
+        appWidgetIds: IntArray
+    ) {
+        for (appWidgetId in appWidgetIds) {
+            updateAppWidget(context, appWidgetManager, appWidgetId)
+        }
+    }
+
+    override fun onAppWidgetOptionsChanged(
+        context: Context,
+        appWidgetManager: AppWidgetManager,
+        appWidgetId: Int,
+        newOptions: Bundle?
+    ) {
+        // Log.v(TAG, "onAppWidgetOptionsChanged: id=${appWidgetId}")
+        updateAppWidget(context, appWidgetManager, appWidgetId)
+        super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions)
+    }
+}
+
+const val TAG = "PaintChips"
+
+val SHADE_NUMBERS = intArrayOf(0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000)
+
+val COLORS_NEUTRAL1 = intArrayOf(
+    android.R.color.system_neutral1_0,
+    android.R.color.system_neutral1_10,
+    android.R.color.system_neutral1_50,
+    android.R.color.system_neutral1_100,
+    android.R.color.system_neutral1_200,
+    android.R.color.system_neutral1_300,
+    android.R.color.system_neutral1_400,
+    android.R.color.system_neutral1_500,
+    android.R.color.system_neutral1_600,
+    android.R.color.system_neutral1_700,
+    android.R.color.system_neutral1_800,
+    android.R.color.system_neutral1_900,
+    android.R.color.system_neutral1_1000
+)
+
+val COLORS_NEUTRAL2 = intArrayOf(
+    android.R.color.system_neutral2_0,
+    android.R.color.system_neutral2_10,
+    android.R.color.system_neutral2_50,
+    android.R.color.system_neutral2_100,
+    android.R.color.system_neutral2_200,
+    android.R.color.system_neutral2_300,
+    android.R.color.system_neutral2_400,
+    android.R.color.system_neutral2_500,
+    android.R.color.system_neutral2_600,
+    android.R.color.system_neutral2_700,
+    android.R.color.system_neutral2_800,
+    android.R.color.system_neutral2_900,
+    android.R.color.system_neutral2_1000
+)
+
+var COLORS_ACCENT1 = intArrayOf(
+    android.R.color.system_accent1_0,
+    android.R.color.system_accent1_10,
+    android.R.color.system_accent1_50,
+    android.R.color.system_accent1_100,
+    android.R.color.system_accent1_200,
+    android.R.color.system_accent1_300,
+    android.R.color.system_accent1_400,
+    android.R.color.system_accent1_500,
+    android.R.color.system_accent1_600,
+    android.R.color.system_accent1_700,
+    android.R.color.system_accent1_800,
+    android.R.color.system_accent1_900,
+    android.R.color.system_accent1_1000
+)
+
+var COLORS_ACCENT2 = intArrayOf(
+    android.R.color.system_accent2_0,
+    android.R.color.system_accent2_10,
+    android.R.color.system_accent2_50,
+    android.R.color.system_accent2_100,
+    android.R.color.system_accent2_200,
+    android.R.color.system_accent2_300,
+    android.R.color.system_accent2_400,
+    android.R.color.system_accent2_500,
+    android.R.color.system_accent2_600,
+    android.R.color.system_accent2_700,
+    android.R.color.system_accent2_800,
+    android.R.color.system_accent2_900,
+    android.R.color.system_accent2_1000
+)
+
+var COLORS_ACCENT3 = intArrayOf(
+    android.R.color.system_accent3_0,
+    android.R.color.system_accent3_10,
+    android.R.color.system_accent3_50,
+    android.R.color.system_accent3_100,
+    android.R.color.system_accent3_200,
+    android.R.color.system_accent3_300,
+    android.R.color.system_accent3_400,
+    android.R.color.system_accent3_500,
+    android.R.color.system_accent3_600,
+    android.R.color.system_accent3_700,
+    android.R.color.system_accent3_800,
+    android.R.color.system_accent3_900,
+    android.R.color.system_accent3_1000
+)
+
+var COLOR_NAMES = arrayOf(
+    "N1", "N2", "A1", "A2", "A3"
+)
+
+var COLORS = arrayOf(
+    COLORS_NEUTRAL1,
+    COLORS_NEUTRAL2,
+    COLORS_ACCENT1,
+    COLORS_ACCENT2,
+    COLORS_ACCENT3
+)
+
+internal fun updateAppWidget(
+    context: Context,
+    appWidgetManager: AppWidgetManager,
+    appWidgetId: Int
+) {
+    // val opts = appWidgetManager.getAppWidgetOptions(appWidgetId)
+    // Log.v(TAG, "requested sizes=${opts[OPTION_APPWIDGET_SIZES]}")
+
+    val allSizes = mapOf(
+        SizeF(50f, 50f)
+                to buildWidget(context, 1, 1, ClickBehavior.LAUNCH),
+        SizeF(100f, 50f)
+                to buildWidget(context, 1, 2, ClickBehavior.LAUNCH),
+        SizeF(150f, 50f)
+                to buildWidget(context, 1, 3, ClickBehavior.LAUNCH),
+        SizeF(200f, 50f)
+                to buildWidget(context, 1, 4, ClickBehavior.LAUNCH),
+        SizeF(250f, 50f)
+                to buildWidget(context, 1, 5, ClickBehavior.LAUNCH),
+
+        SizeF(50f, 120f)
+                to buildWidget(context, 3, 1, ClickBehavior.LAUNCH),
+        SizeF(100f, 120f)
+                to buildWidget(context, 3, 2, ClickBehavior.LAUNCH),
+        SizeF(150f, 120f)
+                to buildWidget(context, 3, 3, ClickBehavior.LAUNCH),
+        SizeF(200f, 120f)
+                to buildWidget(context, 3, 4, ClickBehavior.LAUNCH),
+        SizeF(250f, 120f)
+                to buildWidget(context, 3, 5, ClickBehavior.LAUNCH),
+
+        SizeF(50f, 250f)
+                to buildWidget(context, 5, 1, ClickBehavior.LAUNCH),
+        SizeF(100f, 250f)
+                to buildWidget(context, 5, 2, ClickBehavior.LAUNCH),
+        SizeF(150f, 250f)
+                to buildWidget(context, 5, 3, ClickBehavior.LAUNCH),
+        SizeF(200f, 250f)
+                to buildWidget(context, 5, 4, ClickBehavior.LAUNCH),
+        SizeF(250f, 250f)
+                to buildWidget(context, 5, 5, ClickBehavior.LAUNCH),
+
+        SizeF(300f, 300f)
+                to buildWidget(context, SHADE_NUMBERS.size, COLORS.size, ClickBehavior.LAUNCH)
+    )
+
+    // Instruct the widget manager to update the widget
+    appWidgetManager.updateAppWidget(appWidgetId, RemoteViews(allSizes))
+}
+
+fun buildFullWidget(context: Context, clickable: ClickBehavior): RemoteViews {
+    return buildWidget(context, SHADE_NUMBERS.size, COLORS.size, clickable)
+}
+
+fun buildWidget(context: Context, numShades: Int, numColors: Int, clickable: ClickBehavior):
+        RemoteViews {
+    val grid = RemoteViews(context.packageName, R.layout.paint_chips_grid)
+
+    // shouldn't be necessary but sometimes the RV instructions get played twice in launcher.
+    grid.removeAllViews(R.id.paint_grid)
+
+    grid.setInt(R.id.paint_grid, "setRowCount", numShades)
+    grid.setInt(R.id.paint_grid, "setColumnCount", numColors)
+
+    Log.v(TAG, "building widget: shade rows=$numShades, color columns=$numColors")
+
+    COLORS.forEachIndexed colorLoop@{ i, colorlist ->
+        when (colorlist) {
+            COLORS_NEUTRAL1 -> if (numColors < 2) return@colorLoop
+            COLORS_NEUTRAL2 -> if (numColors < 4) return@colorLoop
+            COLORS_ACCENT2 -> if (numColors < 3) return@colorLoop
+            COLORS_ACCENT3 -> if (numColors < 5) return@colorLoop
+            else -> {} // always do ACCENT1
+        }
+        colorlist.forEachIndexed shadeLoop@{ j, resId ->
+            when (SHADE_NUMBERS[j]) {
+                500 -> {}
+                300, 700 -> if (numShades < 3) return@shadeLoop
+                100, 900 -> if (numShades < 5) return@shadeLoop
+                else -> if (numShades < SHADE_NUMBERS.size) return@shadeLoop
+            }
+            val cell = RemoteViews(context.packageName, R.layout.paint_chip)
+            cell.setTextViewText(R.id.chip, "${COLOR_NAMES[i]}-${SHADE_NUMBERS[j]}")
+            val textColor = if (SHADE_NUMBERS[j] > 500)
+                    colorlist[0]
+                    else colorlist[colorlist.size - 1]
+            cell.setTextColor(R.id.chip, context.getColor(textColor))
+            cell.setColorStateList(R.id.chip, "setBackgroundTintList", resId)
+            val text = """
+                    ${COLOR_NAMES[i]}-${SHADE_NUMBERS[j]} (@${
+                    context.resources.getResourceName(resId) })
+                    currently: #${ String.format("%06x", context.getColor(resId) and 0xFFFFFF) }
+                    """.trimIndent()
+            when (clickable) {
+                ClickBehavior.SHARE -> cell.setOnClickPendingIntent(
+                    R.id.chip,
+                    makeTextSharePendingIntent(context, text)
+                )
+                ClickBehavior.LAUNCH -> cell.setOnClickPendingIntent(
+                    R.id.chip,
+                    makeActivityLaunchPendingIntent(context)
+                )
+                ClickBehavior.NONE -> { }
+            }
+            grid.addView(R.id.paint_grid, cell)
+        }
+    }
+
+    return grid
+}
+
+enum class ClickBehavior {
+    NONE,
+    SHARE,
+    LAUNCH
+}
+
+fun makeTextSharePendingIntent(context: Context, text: String): PendingIntent {
+    val shareIntent: Intent = Intent().apply {
+        action = Intent.ACTION_SEND
+        putExtra(Intent.EXTRA_TEXT, text)
+        type = "text/plain"
+    }
+
+    val chooserIntent = Intent.createChooser(shareIntent, null).apply {
+        identifier = text // incredible quality-of-life improvement, thanks framework team
+    }
+
+    return PendingIntent.getActivity(context, 0, chooserIntent,
+            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+}
+
+fun makeActivityLaunchPendingIntent(context: Context): PendingIntent {
+    return PendingIntent.getActivity(context, 0,
+        Intent().apply {
+            component = ComponentName(context, PaintChipsActivity::class.java)
+            action = Intent.ACTION_MAIN
+        },
+        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+}
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
index f04b0e3..266fc78 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java
@@ -267,26 +267,22 @@
 
     private static void startLottieAnimationWith(LottieAnimationView illustrationView,
             Uri imageUri) {
-        try {
-            final InputStream inputStream =
-                    getInputStreamFromUri(illustrationView.getContext(), imageUri);
-            illustrationView.setAnimation(inputStream, /* cacheKey= */ null);
-            illustrationView.setRepeatCount(LottieDrawable.INFINITE);
-            illustrationView.playAnimation();
-        } catch (IllegalStateException e) {
-            Log.w(TAG, "Invalid illustration image uri: " + imageUri, e);
-        }
+        final InputStream inputStream =
+                getInputStreamFromUri(illustrationView.getContext(), imageUri);
+        illustrationView.setFailureListener(
+                result -> Log.w(TAG, "Invalid illustration image uri: " + imageUri, result));
+        illustrationView.setAnimation(inputStream, /* cacheKey= */ null);
+        illustrationView.setRepeatCount(LottieDrawable.INFINITE);
+        illustrationView.playAnimation();
     }
 
     private static void startLottieAnimationWith(LottieAnimationView illustrationView,
             @RawRes int rawRes) {
-        try {
-            illustrationView.setAnimation(rawRes);
-            illustrationView.setRepeatCount(LottieDrawable.INFINITE);
-            illustrationView.playAnimation();
-        } catch (IllegalStateException e) {
-            Log.w(TAG, "Invalid illustration resource id: " + rawRes, e);
-        }
+        illustrationView.setFailureListener(
+                result -> Log.w(TAG, "Invalid illustration resource id: " + rawRes, result));
+        illustrationView.setAnimation(rawRes);
+        illustrationView.setRepeatCount(LottieDrawable.INFINITE);
+        illustrationView.playAnimation();
     }
 
     private static void resetAnimations(LottieAnimationView illustrationView) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
index ea9be04..9e3312a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -130,4 +132,27 @@
 
         verify(drawable).start();
     }
+
+    @Test
+    public void playLottieAnimationWithUri_verifyFailureListener() {
+        doReturn(null).when(mAnimationView).getDrawable();
+
+        mPreference.setImageUri(mImageUri);
+        mPreference.onBindViewHolder(mViewHolder);
+
+        verify(mAnimationView).setFailureListener(any());
+    }
+
+    @Test
+    public void playLottieAnimationWithResource_verifyFailureListener() {
+        // fake the valid lottie image
+        final int fakeValidResId = 111;
+        doNothing().when(mAnimationView).setImageResource(fakeValidResId);
+        doReturn(null).when(mAnimationView).getDrawable();
+
+        mPreference.setLottieAnimationResId(fakeValidResId);
+        mPreference.onBindViewHolder(mViewHolder);
+
+        verify(mAnimationView).setFailureListener(any());
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index eb81961..01ae1e9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -76,5 +76,7 @@
         Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
         Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
         Settings.Global.POWER_BUTTON_LONG_PRESS,
+        Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
+        Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 5220a04..3c7d7a8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -140,6 +140,8 @@
                         /* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
         VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Global.AUTOMATIC_POWER_SAVE_MODE, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.ADVANCED_BATTERY_USAGE_AMOUNT, PERCENTAGE_INTEGER_VALIDATOR);
     }
 }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3297937..9362a18 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -137,7 +137,6 @@
                     Settings.Global.AUTOFILL_LOGGING_LEVEL,
                     Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE,
                     Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS,
-                    Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
                     Settings.Global.AVERAGE_TIME_TO_DISCHARGE,
                     Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY,
                     Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME,
@@ -592,8 +591,7 @@
                     Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
                     Settings.Global.CACHED_APPS_FREEZER_ENABLED,
                     Settings.Global.APP_INTEGRITY_VERIFICATION_TIMEOUT,
-                    Settings.Global.KEY_CHORD_POWER_VOLUME_UP,
-                    Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT);
+                    Settings.Global.KEY_CHORD_POWER_VOLUME_UP);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/SystemUI/res/drawable-nodpi/android_12.xml b/packages/SystemUI/res/drawable-nodpi/android_12.xml
new file mode 100644
index 0000000..bdeeced
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/android_12.xml
@@ -0,0 +1,37 @@
+<!--
+Copyright (C) 2021 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2 (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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <group>
+    <clip-path
+        android:pathData="M14,14h21v20h-21z"/>
+    <path
+        android:pathData="M15,15C15.7956,15 16.5587,15.3161 17.1213,15.8787C17.6839,16.4413 18,17.2044 18,18V33"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#ffffff"
+        android:strokeLineCap="round"/>
+    <path
+        android:pathData="M34,33H22V30C22,28.4087 22.6321,26.8826 23.7574,25.7574C24.8826,24.6321 26.4087,24 28,24H31C31.7956,24 32.5587,23.6839 33.1213,23.1213C33.6839,22.5587 34,21.7957 34,21C34.009,19.5779 33.5126,18.1989 32.5993,17.1088C31.686,16.0188 30.4153,15.2885 29.0136,15.0483C27.612,14.8081 26.1706,15.0735 24.9464,15.7973C23.7223,16.5211 22.795,17.6561 22.33,19C22.2199,19.3261 22.1363,19.6605 22.08,20"
+        android:strokeWidth="2"
+        android:fillColor="#00000000"
+        android:strokeColor="#ffffff"
+        android:strokeLineCap="round"/>
+  </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml
index 7f8d4fa..9972496 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon.xml
@@ -15,5 +15,5 @@
 -->
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@drawable/icon_bg"/>
-    <foreground android:drawable="@drawable/android_11_dial"/>
+    <foreground android:drawable="@drawable/android_12"/>
 </adaptive-icon>
diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
index 31b2a7f..ff7cbae 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
@@ -14,5 +14,5 @@
     limitations under the License.
 -->
 <color xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#073042" />
+    android:color="@android:color/system_accent1_500" />
 
diff --git a/packages/SystemUI/res/drawable/controls_icon.xml b/packages/SystemUI/res/drawable/controls_icon.xml
new file mode 100644
index 0000000..f1814a2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/controls_icon.xml
@@ -0,0 +1,25 @@
+<?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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M12,3L4,9v12h16L20,9l-8,-6zM18,19h-3v-6L9,13v6L6,19v-9l6,-4.5 6,4.5v9z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/wallet_lockscreen_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/wallet_lockscreen_bg.xml
rename to packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 9ce83a7..8dbd59d 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -82,18 +82,32 @@
 
     <ImageView
         android:id="@+id/wallet_button"
-        android:layout_height="@dimen/keyguard_affordance_wallet_height"
-        android:layout_width="@dimen/keyguard_affordance_wallet_width"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
         android:layout_gravity="bottom|end"
         android:scaleType="center"
         android:tint="?android:attr/textColorPrimary"
         android:src="@drawable/ic_wallet_lockscreen"
-        android:background="@drawable/wallet_lockscreen_bg"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
         android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
         android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
         android:contentDescription="@string/accessibility_wallet_button"
         android:visibility="gone" />
 
+    <ImageView
+        android:id="@+id/controls_button"
+        android:layout_height="@dimen/keyguard_affordance_fixed_height"
+        android:layout_width="@dimen/keyguard_affordance_fixed_width"
+        android:layout_gravity="bottom|start"
+        android:scaleType="center"
+        android:tint="?android:attr/textColorPrimary"
+        android:src="@drawable/controls_icon"
+        android:background="@drawable/keyguard_bottom_affordance_bg"
+        android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+        android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
+        android:contentDescription="@string/quick_controls_title"
+        android:visibility="gone" />
+
     <FrameLayout
         android:id="@+id/overlay_container"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values-ar/strings_tv.xml b/packages/SystemUI/res/values-ar/strings_tv.xml
index f6c4aef..13f23f0 100644
--- a/packages/SystemUI/res/values-ar/strings_tv.xml
+++ b/packages/SystemUI/res/values-ar/strings_tv.xml
@@ -26,16 +26,10 @@
     <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"عبر <xliff:g id="VPN_APP">%1$s</xliff:g>"</string>
     <string name="tv_notification_panel_title" msgid="5311050946506276154">"الإشعارات"</string>
     <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"ما من إشعارات"</string>
-    <!-- no translation found for mic_recording_announcement (7587123608060316575) -->
-    <skip />
-    <!-- no translation found for camera_recording_announcement (7240177719403759112) -->
-    <skip />
-    <!-- no translation found for mic_and_camera_recording_announcement (8599231390508812667) -->
-    <skip />
-    <!-- no translation found for mic_stopped_recording_announcement (7301537004900721242) -->
-    <skip />
-    <!-- no translation found for camera_stopped_recording_announcement (8540496432367032801) -->
-    <skip />
-    <!-- no translation found for mic_camera_stopped_recording_announcement (8708524579599977412) -->
-    <skip />
+    <string name="mic_recording_announcement" msgid="7587123608060316575">"جارٍ التسجيل بالميكرفون"</string>
+    <string name="camera_recording_announcement" msgid="7240177719403759112">"جارٍ التسجيل بالكاميرا"</string>
+    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"جارٍ التسجيل بالكاميرا والميكروفون"</string>
+    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"توقف التسجيل بالميكرفون."</string>
+    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"توقف التسجيل بالكاميرا."</string>
+    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"توقف التسجيل بالكاميرا والميكروفون."</string>
 </resources>
diff --git a/packages/SystemUI/res/values-as/strings_tv.xml b/packages/SystemUI/res/values-as/strings_tv.xml
index 6ceee80..733e2e6 100644
--- a/packages/SystemUI/res/values-as/strings_tv.xml
+++ b/packages/SystemUI/res/values-as/strings_tv.xml
@@ -26,16 +26,10 @@
     <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g>ৰ জৰিয়তে"</string>
     <string name="tv_notification_panel_title" msgid="5311050946506276154">"জাননী"</string>
     <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"কোনো জাননী নাই"</string>
-    <!-- no translation found for mic_recording_announcement (7587123608060316575) -->
-    <skip />
-    <!-- no translation found for camera_recording_announcement (7240177719403759112) -->
-    <skip />
-    <!-- no translation found for mic_and_camera_recording_announcement (8599231390508812667) -->
-    <skip />
-    <!-- no translation found for mic_stopped_recording_announcement (7301537004900721242) -->
-    <skip />
-    <!-- no translation found for camera_stopped_recording_announcement (8540496432367032801) -->
-    <skip />
-    <!-- no translation found for mic_camera_stopped_recording_announcement (8708524579599977412) -->
-    <skip />
+    <string name="mic_recording_announcement" msgid="7587123608060316575">"মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰি আছে"</string>
+    <string name="camera_recording_announcement" msgid="7240177719403759112">"কেমেৰাটোৱে ৰেক’ৰ্ড কৰি আছে"</string>
+    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"কেমেৰা আৰু মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰি আছে"</string>
+    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰাটো বন্ধ কৰিছে"</string>
+    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"কেমেৰাটোৱে ৰেক’ৰ্ড কৰাটো বন্ধ কৰিছে"</string>
+    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"কেমেৰা আৰু মাইক্ৰ’ফ’নটোৱে ৰেক’ৰ্ড কৰাটো বন্ধ কৰিছে"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-gu/strings_tv.xml b/packages/SystemUI/res/values-gu/strings_tv.xml
index c2c8ad6..e226503 100644
--- a/packages/SystemUI/res/values-gu/strings_tv.xml
+++ b/packages/SystemUI/res/values-gu/strings_tv.xml
@@ -26,16 +26,10 @@
     <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> મારફતે"</string>
     <string name="tv_notification_panel_title" msgid="5311050946506276154">"નોટિફિકેશન"</string>
     <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"કોઈ નોટિફિકેશન નથી"</string>
-    <!-- no translation found for mic_recording_announcement (7587123608060316575) -->
-    <skip />
-    <!-- no translation found for camera_recording_announcement (7240177719403759112) -->
-    <skip />
-    <!-- no translation found for mic_and_camera_recording_announcement (8599231390508812667) -->
-    <skip />
-    <!-- no translation found for mic_stopped_recording_announcement (7301537004900721242) -->
-    <skip />
-    <!-- no translation found for camera_stopped_recording_announcement (8540496432367032801) -->
-    <skip />
-    <!-- no translation found for mic_camera_stopped_recording_announcement (8708524579599977412) -->
-    <skip />
+    <string name="mic_recording_announcement" msgid="7587123608060316575">"માઇક્રોફોનનું રેકોર્ડિંગ ચાલુ છે"</string>
+    <string name="camera_recording_announcement" msgid="7240177719403759112">"કૅમેરાનું રેકોર્ડિંગ ચાલુ છે"</string>
+    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"કૅમેરા અને માઇક્રોફોનનું રેકોર્ડિંગ ચાલુ છે"</string>
+    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"માઇક્રોફોનનું રેકોર્ડિંગ બંધ થઈ ગયું"</string>
+    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"કૅમેરાનું રેકોર્ડિંગ બંધ થઈ ગયું"</string>
+    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"કૅમેરા અને માઇક્રોફોનનું રેકોર્ડિંગ બંધ થઈ ગયું"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-hi/strings_tv.xml b/packages/SystemUI/res/values-hi/strings_tv.xml
index 1135ecb..cc9a562 100644
--- a/packages/SystemUI/res/values-hi/strings_tv.xml
+++ b/packages/SystemUI/res/values-hi/strings_tv.xml
@@ -26,10 +26,10 @@
     <string name="notification_disclosure_vpn_text" msgid="3873532735584866236">"<xliff:g id="VPN_APP">%1$s</xliff:g> के ज़रिए"</string>
     <string name="tv_notification_panel_title" msgid="5311050946506276154">"सूचनाएं"</string>
     <string name="tv_notification_panel_no_notifications" msgid="9115191912267270678">"कोई सूचना नहीं है"</string>
-    <string name="mic_recording_announcement" msgid="7587123608060316575">"माइक्रोफ़ोन आवाज़ रिकॉर्ड कर रहा है"</string>
-    <string name="camera_recording_announcement" msgid="7240177719403759112">"कैमरा वीडियो रिकॉर्ड कर रहा है"</string>
-    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"कैमरा और माइक्रोफ़ोन वीडियो रिकॉर्ड कर रहे हैं"</string>
-    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"माइक्रोफ़ोन ने आवाज़ रिकॉर्ड करना बंद कर दिया है"</string>
-    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"कैमरे ने वीडियो रिकॉर्ड करना बंद कर दिया है"</string>
-    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"कैमरे और माइक्रोफ़ोन ने वीडियो रिकॉर्ड करना बंद कर दिया है"</string>
+    <string name="mic_recording_announcement" msgid="7587123608060316575">"माइक्रोफ़ोन रिकॉर्ड कर रहा है"</string>
+    <string name="camera_recording_announcement" msgid="7240177719403759112">"कैमरा रिकॉर्ड कर रहा है"</string>
+    <string name="mic_and_camera_recording_announcement" msgid="8599231390508812667">"कैमरा और माइक्रोफ़ोन रिकॉर्ड कर रहे हैं"</string>
+    <string name="mic_stopped_recording_announcement" msgid="7301537004900721242">"माइक्रोफ़ोन ने रिकॉर्ड करना बंद कर दिया है"</string>
+    <string name="camera_stopped_recording_announcement" msgid="8540496432367032801">"कैमरे ने रिकॉर्ड करना बंद कर दिया है"</string>
+    <string name="mic_camera_stopped_recording_announcement" msgid="8708524579599977412">"कैमरे और माइक्रोफ़ोन ने रिकॉर्ड करना बंद कर दिया है"</string>
 </resources>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 044086e..9aaea76 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -353,7 +353,7 @@
     <string name="quick_settings_location_off_label" msgid="7923929131443915919">"위치 사용 중지"</string>
     <string name="quick_settings_camera_label" msgid="5612076679385269339">"카메라 액세스"</string>
     <string name="quick_settings_mic_label" msgid="8392773746295266375">"마이크 액세스"</string>
-    <string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"허용됨"</string>
+    <string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"사용 가능"</string>
     <string name="quick_settings_camera_mic_blocked" msgid="4710884905006788281">"차단됨"</string>
     <string name="quick_settings_media_device_label" msgid="8034019242363789941">"미디어 기기"</string>
     <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string>
diff --git a/packages/SystemUI/res/values-ko/tiles_states_strings.xml b/packages/SystemUI/res/values-ko/tiles_states_strings.xml
index 38c0185..b583f24 100644
--- a/packages/SystemUI/res/values-ko/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-ko/tiles_states_strings.xml
@@ -48,8 +48,8 @@
   </string-array>
   <string-array name="tile_states_battery">
     <item msgid="6311253873330062961">"이용 불가"</item>
-    <item msgid="7838121007534579872">"꺼짐"</item>
-    <item msgid="1578872232501319194">"켜짐"</item>
+    <item msgid="7838121007534579872">"사용 안함"</item>
+    <item msgid="1578872232501319194">"사용"</item>
   </string-array>
   <string-array name="tile_states_dnd">
     <item msgid="467587075903158357">"이용 불가"</item>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 5317bca..9a8de86 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -243,7 +243,7 @@
     <skip />
     <!-- no translation found for accessibility_work_mode (1280025758672376313) -->
     <skip />
-    <string name="accessibility_notification_dismissed" msgid="4411652015138892952">"Известувањето е отфрлено."</string>
+    <string name="accessibility_notification_dismissed" msgid="4411652015138892952">"Известувањето е отфрлено"</string>
     <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"Панел за известување"</string>
     <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"Брзи поставки."</string>
     <string name="accessibility_desc_lock_screen" msgid="5983125095181194887">"Заклучи екран."</string>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index 369e45c..ab159e1 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -18,10 +18,18 @@
     <!-- Max number of columns for quick controls area -->
     <integer name="controls_max_columns">2</integer>
 
-    <integer name="quick_settings_num_columns">2</integer>
+    <!-- The maximum number of rows in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_rows">4</integer>
+
+    <!-- The maximum number of tiles in the QuickQSPanel -->
     <integer name="quick_qs_panel_max_tiles">8</integer>
 
     <!-- Whether to use the split 2-column notification shade -->
     <bool name="config_use_split_notification_shade">true</bool>
+
+    <!-- The number of columns in the QuickSettings -->
+    <integer name="quick_settings_num_columns">2</integer>
+
+    <!-- Notifications are sized to match the width of two (of 4) qs tiles in landscape. -->
+    <bool name="config_skinnyNotifsInLandscape">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 942cb2b..45b5afa 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -40,4 +40,8 @@
 
     <!-- How many lines to show in the security footer -->
     <integer name="qs_security_footer_maxLines">1</integer>
+
+    <!-- Determines whether to allow the nav bar handle to be forced to be opaque. -->
+    <bool name="allow_force_nav_bar_handle_opaque">false</bool>
+
 </resources>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 020d9b4..b8a3ccd 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -957,7 +957,7 @@
     <string name="tuner_right" msgid="8247571132790812149">"కుడి"</string>
     <string name="tuner_menu" msgid="363690665924769420">"మెనూ"</string>
     <string name="tuner_app" msgid="6949280415826686972">"<xliff:g id="APP">%1$s</xliff:g> అనురవర్తనం"</string>
-    <string name="notification_channel_alerts" msgid="3385787053375150046">"హెచ్చరికలు"</string>
+    <string name="notification_channel_alerts" msgid="3385787053375150046">"అలర్ట్‌లు"</string>
     <string name="notification_channel_battery" msgid="9219995638046695106">"బ్యాటరీ"</string>
     <string name="notification_channel_screenshot" msgid="7665814998932211997">"స్క్రీన్‌షాట్‌లు"</string>
     <string name="notification_channel_general" msgid="4384774889645929705">"సాధారణ సందేశాలు"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ce469f7..e8a6c16 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -353,7 +353,7 @@
     <string name="quick_settings_location_off_label" msgid="7923929131443915919">"ปิดตำแหน่ง"</string>
     <string name="quick_settings_camera_label" msgid="5612076679385269339">"สิทธิ์เข้าถึงกล้อง"</string>
     <string name="quick_settings_mic_label" msgid="8392773746295266375">"สิทธิ์เข้าถึงไมโครโฟน"</string>
-    <string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"มี"</string>
+    <string name="quick_settings_camera_mic_available" msgid="1453719768420394314">"พร้อมให้ใช้งาน"</string>
     <string name="quick_settings_camera_mic_blocked" msgid="4710884905006788281">"ถูกบล็อก"</string>
     <string name="quick_settings_media_device_label" msgid="8034019242363789941">"อุปกรณ์สื่อ"</string>
     <string name="quick_settings_rssi_label" msgid="3397615415140356701">"RSSI"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 06e4483..2ee0026 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -916,8 +916,8 @@
     <dimen name="keyguard_affordance_height">48dp</dimen>
     <dimen name="keyguard_affordance_width">48dp</dimen>
 
-    <dimen name="keyguard_affordance_wallet_height">48dp</dimen>
-    <dimen name="keyguard_affordance_wallet_width">48dp</dimen>
+    <dimen name="keyguard_affordance_fixed_height">48dp</dimen>
+    <dimen name="keyguard_affordance_fixed_width">48dp</dimen>
 
     <dimen name="keyguard_affordance_horizontal_offset">32dp</dimen>
     <dimen name="keyguard_affordance_vertical_offset">32dp</dimen>
@@ -931,6 +931,7 @@
     <dimen name="keyguard_lock_padding">20dp</dimen>
 
     <dimen name="keyguard_indication_margin_bottom">32dp</dimen>
+    <dimen name="lock_icon_margin_bottom">98dp</dimen>
 
     <!-- The text size for battery level -->
     <dimen name="battery_level_text_size">12sp</dimen>
@@ -1275,6 +1276,8 @@
     <!--  Three privacy items. This value must not be exceeded  -->
     <dimen name="ongoing_appops_chip_max_width">76dp</dimen>
     <dimen name="ongoing_appops_dot_diameter">6dp</dimen>
+    <!--  Total minimum padding to enforce to ensure that the dot can always show  -->
+    <dimen name="ongoing_appops_dot_min_padding">20dp</dimen>
 
     <dimen name="ongoing_appops_dialog_side_margins">@dimen/notification_shade_content_margin_horizontal</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 82d6302..62688ca 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1064,6 +1064,9 @@
     <!-- Message shown when lock screen is tapped or face authentication fails. [CHAR LIMIT=60] -->
     <string name="keyguard_unlock">Swipe up to open</string>
 
+    <!-- Message shown when lock screen is tapped or face authentication fails. Provides extra instructions for how the user can enter their device (unlock or proceed to home) [CHAR LIMIT=60] -->
+    <string name="keyguard_unlock_press">Press to open</string>
+
     <!-- Message shown when face authentication fails and the pin pad is visible. [CHAR LIMIT=60] -->
     <string name="keyguard_retry">Swipe up to try again</string>
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 70ed817..51eabf6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -764,6 +764,8 @@
         <item name="android:windowBackground">@android:color/black</item>
         <item name="android:windowAnimationStyle">@null</item>
         <item name="android:statusBarColor">@android:color/black</item>
+        <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
         <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
     </style>
 
@@ -898,5 +900,7 @@
     <style name="Wallet.Theme" parent="@android:style/Theme.DeviceDefault">
       <item name="android:colorBackground">@android:color/system_neutral1_900</item>
       <item name="android:itemBackground">@android:color/system_neutral1_800</item>
+      <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen.  -->
+      <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
     </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0e25fac..5ef7143 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2068,6 +2068,15 @@
     }
 
     /**
+     * @return if udfps is available on this device. will return true even if the user hasn't
+     * enrolled udfps.
+     */
+    public boolean isUdfpsAvailable() {
+        return mAuthController.getUdfpsProps() != null
+                && !mAuthController.getUdfpsProps().isEmpty();
+    }
+
+    /**
      * @return true if there's at least one face enrolled
      */
     public boolean isFaceEnrolled() {
@@ -2398,8 +2407,10 @@
             if (isEncryptedOrLockdown(userId) && supportsFaceDetection) {
                 mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
             } else {
+                final boolean isBypassEnabled = mKeyguardBypassController != null
+                        && mKeyguardBypassController.isBypassEnabled();
                 mFaceManager.authenticate(null /* crypto */, mFaceCancelSignal,
-                        mFaceAuthenticationCallback, null /* handler */, userId);
+                        mFaceAuthenticationCallback, null /* handler */, userId, isBypassEnabled);
             }
             setFaceRunningState(BIOMETRIC_STATE_RUNNING);
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 8b974b4..9c8582fa 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -72,7 +72,10 @@
  */
 @StatusBarComponent.StatusBarScope
 public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
-
+    private static final float sDefaultDensity =
+            (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT;
+    private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36);
+    private static final float sDistAboveKgBottomAreaPx = sDefaultDensity * 12;
     private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
             new AudioAttributes.Builder()
                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -111,9 +114,7 @@
     private boolean mHasUdfps;
     private float mHeightPixels;
     private float mWidthPixels;
-    private float mDensity;
-    private int mAmbientIndicationHeight; // in pixels
-    private int mKgIndicationHeight; // in pixels
+    private int mBottomPadding; // in pixels
 
     private boolean mShowUnlockIcon;
     private boolean mShowLockIcon;
@@ -318,11 +319,8 @@
         final DisplayMetrics metrics = mView.getContext().getResources().getDisplayMetrics();
         mWidthPixels = metrics.widthPixels;
         mHeightPixels = metrics.heightPixels;
-        mDensity = metrics.density;
-        mKgIndicationHeight = mView.getContext().getResources().getDimensionPixelSize(
-                R.dimen.keyguard_indication_margin_bottom)
-            + mView.getContext().getResources().getDimensionPixelSize(
-                R.dimen.keyguard_indication_bottom_padding);
+        mBottomPadding = mView.getContext().getResources().getDimensionPixelSize(
+                R.dimen.lock_icon_margin_bottom);
         updateLockIconLocation();
     }
 
@@ -332,26 +330,15 @@
             mView.setCenterLocation(new PointF(props.sensorLocationX, props.sensorLocationY),
                     props.sensorRadius);
         } else {
-            final float distAboveKgBottomArea = 12 * mDensity;
-            final float radius = 36 * mDensity;
-            final int kgBottomAreaHeight = Math.max(mKgIndicationHeight, mAmbientIndicationHeight);
             mView.setCenterLocation(
                     new PointF(mWidthPixels / 2,
-                        mHeightPixels - kgBottomAreaHeight - distAboveKgBottomArea
-                            - radius / 2), (int) radius);
+                        mHeightPixels - mBottomPadding - sDistAboveKgBottomAreaPx
+                            - sLockIconRadiusPx), sLockIconRadiusPx);
         }
 
         mView.getHitRect(mSensorTouchLocation);
     }
 
-    /**
-     * Set the location of ambient indication if showing (ie: now playing)
-     */
-    public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
-        mAmbientIndicationHeight = ambientIndicationBottomPadding;
-        updateLockIconLocation();
-    }
-
     @Override
     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 5418770..f8c57bf 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -98,6 +98,7 @@
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 import com.android.systemui.statusbar.policy.AccessibilityController;
@@ -358,6 +359,7 @@
     @Inject Lazy<EdgeBackGestureHandler.Factory> mEdgeBackGestureHandlerFactoryLazy;
     @Inject Lazy<UiEventLogger> mUiEventLogger;
     @Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
+    @Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
 
     @Inject
     public Dependency() {
@@ -572,6 +574,7 @@
                 mEdgeBackGestureHandlerFactoryLazy::get);
         mProviders.put(UiEventLogger.class, mUiEventLogger::get);
         mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
+        mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
 
         Dependency.setInstance(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 0ae89bc..b5b6b13 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -16,25 +16,24 @@
 
 package com.android.systemui;
 
-import android.app.ActivityManager;
-import android.app.Dialog;
+import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.UserInfo;
-import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.util.settings.SecureSettings;
 
 /**
  * Manages notification when a guest session is resumed.
@@ -43,16 +42,23 @@
 
     private static final String TAG = "GuestResumeSessionReceiver";
 
-    private static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
+    @VisibleForTesting
+    public static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
 
-    private Dialog mNewSessionDialog;
+    @VisibleForTesting
+    public AlertDialog mNewSessionDialog;
+    private final UserTracker mUserTracker;
     private final UserSwitcherController mUserSwitcherController;
     private final UiEventLogger mUiEventLogger;
+    private final SecureSettings mSecureSettings;
 
     public GuestResumeSessionReceiver(UserSwitcherController userSwitcherController,
-            UiEventLogger uiEventLogger) {
+            UserTracker userTracker, UiEventLogger uiEventLogger,
+            SecureSettings secureSettings) {
         mUserSwitcherController = userSwitcherController;
+        mUserTracker = userTracker;
         mUiEventLogger = uiEventLogger;
+        mSecureSettings = secureSettings;
     }
 
     /**
@@ -78,26 +84,19 @@
                 return;
             }
 
-            UserInfo currentUser;
-            try {
-                currentUser = ActivityManager.getService().getCurrentUser();
-            } catch (RemoteException e) {
-                return;
-            }
+            UserInfo currentUser = mUserTracker.getUserInfo();
             if (!currentUser.isGuest()) {
                 return;
             }
 
-            ContentResolver cr = context.getContentResolver();
-            int notFirstLogin = Settings.System.getIntForUser(
-                    cr, SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
+            int notFirstLogin = mSecureSettings.getIntForUser(
+                    SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
             if (notFirstLogin != 0) {
                 mNewSessionDialog = new ResetSessionDialog(context, mUserSwitcherController,
                         mUiEventLogger, userId);
                 mNewSessionDialog.show();
             } else {
-                Settings.System.putIntForUser(
-                        cr, SETTING_GUEST_HAS_LOGGED_IN, 1, userId);
+                mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, 1, userId);
             }
         }
     }
@@ -109,18 +108,26 @@
         }
     }
 
-    private static class ResetSessionDialog extends SystemUIDialog implements
+    /**
+     * Dialog shown when user when asking for confirmation before deleting guest user.
+     */
+    @VisibleForTesting
+    public static class ResetSessionDialog extends SystemUIDialog implements
             DialogInterface.OnClickListener {
 
-        private static final int BUTTON_WIPE = BUTTON_NEGATIVE;
-        private static final int BUTTON_DONTWIPE = BUTTON_POSITIVE;
+        @VisibleForTesting
+        public static final int BUTTON_WIPE = BUTTON_NEGATIVE;
+        @VisibleForTesting
+        public static final int BUTTON_DONTWIPE = BUTTON_POSITIVE;
 
         private final UserSwitcherController mUserSwitcherController;
         private final UiEventLogger mUiEventLogger;
         private final int mUserId;
 
-        ResetSessionDialog(Context context, UserSwitcherController userSwitcherController,
-                UiEventLogger uiEventLogger, int userId) {
+        ResetSessionDialog(Context context,
+                UserSwitcherController userSwitcherController,
+                UiEventLogger uiEventLogger,
+                int userId) {
             super(context);
 
             setTitle(context.getString(R.string.guest_wipe_session_title));
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e9c5653..f653088 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -797,8 +797,8 @@
     }
 
     static boolean shouldDrawCutout(Context context) {
-        return context.getResources().getBoolean(
-                com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
+        return DisplayCutout.getFillBuiltInDisplayCutout(
+                context.getResources(), context.getDisplay().getUniqueId());
     }
 
     private void updateLayoutParams() {
@@ -1085,7 +1085,8 @@
             int dw = flipped ? lh : lw;
             int dh = flipped ? lw : lh;
 
-            Path path = DisplayCutout.pathFromResources(getResources(), dw, dh);
+            Path path = DisplayCutout.pathFromResources(
+                    getResources(), getDisplay().getUniqueId(), dw, dh);
             if (path != null) {
                 mBoundingPath.set(path);
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index c241c08..4104e31 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -64,16 +64,12 @@
         }
     }
 
-    override fun onStart() {
-        super.onStart()
+    override fun onResume() {
+        super.onResume()
 
         parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
         parent.alpha = 0f
         uiController.show(parent, { finish() }, this)
-    }
-
-    override fun onResume() {
-        super.onResume()
 
         ControlsAnimations.enterAnimation(parent).start()
     }
@@ -82,8 +78,8 @@
         finish()
     }
 
-    override fun onStop() {
-        super.onStop()
+    override fun onPause() {
+        super.onPause()
 
         uiController.hide()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 62b92cb..391e6de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -18,14 +18,26 @@
 
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TransitionOldType;
+import static android.view.WindowManager.TransitionType;
 
+import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.Service;
+import android.app.WindowConfiguration;
 import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Debug;
@@ -42,8 +54,13 @@
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionFilter;
+import android.window.TransitionInfo;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IKeyguardDrawnCallback;
@@ -51,8 +68,11 @@
 import com.android.internal.policy.IKeyguardService;
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.systemui.SystemUIApplication;
+import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.ArrayList;
+
 import javax.inject.Inject;
 
 public class KeyguardService extends Service {
@@ -79,40 +99,163 @@
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
      */
     public static boolean sEnableRemoteKeyguardGoingAwayAnimation =
-            !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 1;
+            sEnableRemoteKeyguardAnimation >= 1;
 
     /**
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
      */
     public static boolean sEnableRemoteKeyguardOccludeAnimation =
-            !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 2;
+            sEnableRemoteKeyguardAnimation >= 2;
 
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
 
+    private static int newModeToLegacyMode(int newMode) {
+        switch (newMode) {
+            case WindowManager.TRANSIT_OPEN:
+            case WindowManager.TRANSIT_TO_FRONT:
+                return MODE_OPENING;
+            case WindowManager.TRANSIT_CLOSE:
+            case WindowManager.TRANSIT_TO_BACK:
+                return MODE_CLOSING;
+            default:
+                return 2; // MODE_CHANGING
+        }
+    }
+
+    private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers) {
+        final ArrayList<RemoteAnimationTarget> out = new ArrayList<>();
+        for (int i = 0; i < info.getChanges().size(); i++) {
+            boolean changeIsWallpaper =
+                    (info.getChanges().get(i).getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
+            if (wallpapers != changeIsWallpaper) continue;
+
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+            final int taskId = taskInfo != null ? change.getTaskInfo().taskId : -1;
+            boolean isNotInRecents;
+            WindowConfiguration windowConfiguration = null;
+            if (taskInfo != null) {
+                if (taskInfo.getConfiguration() != null) {
+                    windowConfiguration =
+                            change.getTaskInfo().getConfiguration().windowConfiguration;
+                }
+                isNotInRecents = !change.getTaskInfo().isRunning;
+            } else {
+                isNotInRecents = true;
+            }
+            Rect localBounds = new Rect(change.getEndAbsBounds());
+            localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
+
+            out.add(new RemoteAnimationTarget(
+                    taskId,
+                    newModeToLegacyMode(change.getMode()),
+                    change.getLeash(),
+                    (change.getFlags() & TransitionInfo.FLAG_TRANSLUCENT) != 0
+                            || (change.getFlags() & TransitionInfo.FLAG_SHOW_WALLPAPER) != 0,
+                    null /* clipRect */,
+                    new Rect(0, 0, 0, 0) /* contentInsets */,
+                    info.getChanges().size() - i,
+                    new Point(), localBounds, new Rect(change.getEndAbsBounds()),
+                    windowConfiguration, isNotInRecents, null /* startLeash */,
+                    change.getStartAbsBounds(), taskInfo));
+        }
+        return out.toArray(new RemoteAnimationTarget[out.size()]);
+    }
+
+    private static @TransitionOldType int getTransitionOldType(@TransitionType int type,
+            RemoteAnimationTarget[] apps) {
+        if (type == TRANSIT_KEYGUARD_GOING_AWAY) {
+            return apps.length == 0 ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+                    : TRANSIT_OLD_KEYGUARD_GOING_AWAY;
+        } else if (type == TRANSIT_KEYGUARD_OCCLUDE) {
+            return TRANSIT_OLD_KEYGUARD_OCCLUDE;
+        } else if (type == TRANSIT_KEYGUARD_UNOCCLUDE) {
+            return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
+        } else {
+            Slog.d(TAG, "Unexpected transit type: " + type);
+            return TRANSIT_OLD_NONE;
+        }
+    }
+
+    private static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
+        return new IRemoteTransition.Stub() {
+            @Override
+            public void startAnimation(IBinder transition, TransitionInfo info,
+                    SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
+                    throws RemoteException {
+                Slog.d(TAG, "Starts IRemoteAnimationRunner: info=" + info);
+                final RemoteAnimationTarget[] apps = wrap(info, false /* wallpapers */);
+                final RemoteAnimationTarget[] wallpapers = wrap(info, true /* wallpapers */);
+                final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
+
+                // TODO: Remove this, and update alpha value in the IAnimationRunner.
+                for (TransitionInfo.Change change : info.getChanges()) {
+                    t.setAlpha(change.getLeash(), 1.0f);
+                }
+                t.apply();
+                runner.onAnimationStart(getTransitionOldType(info.getType(), apps),
+                        apps, wallpapers, nonApps,
+                        new IRemoteAnimationFinishedCallback.Stub() {
+                            @Override
+                            public void onAnimationFinished() throws RemoteException {
+                                Slog.d(TAG, "Finish IRemoteAnimationRunner.");
+                                finishCallback.onTransitionFinished(null /* wct */, null /* t */);
+                            }
+                        }
+                );
+            }
+
+            public void mergeAnimation(IBinder transition, TransitionInfo info,
+                    SurfaceControl.Transaction t, IBinder mergeTarget,
+                    IRemoteTransitionFinishedCallback finishCallback) {
+
+            }
+        };
+    }
+
     @Inject
     public KeyguardService(KeyguardViewMediator keyguardViewMediator,
-                           KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher) {
+                           KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
+                           ShellTransitions shellTransitions) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
 
-        RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
-        if (sEnableRemoteKeyguardGoingAwayAnimation) {
-            final RemoteAnimationAdapter exitAnimationAdapter =
-                    new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
-            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, exitAnimationAdapter);
-            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
-                    exitAnimationAdapter);
+        if (shellTransitions != null && Transitions.ENABLE_SHELL_TRANSITIONS) {
+            if (sEnableRemoteKeyguardGoingAwayAnimation) {
+                Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_GOING_AWAY");
+                TransitionFilter f = new TransitionFilter();
+                f.mTypeSet = new int[]{TRANSIT_KEYGUARD_GOING_AWAY};
+                shellTransitions.registerRemote(f, wrap(mExitAnimationRunner));
+            }
+            if (sEnableRemoteKeyguardOccludeAnimation) {
+                Slog.d(TAG, "KeyguardService registerRemote: TRANSIT_KEYGUARD_(UN)OCCLUDE");
+                TransitionFilter f = new TransitionFilter();
+                f.mTypeSet = new int[]{TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_KEYGUARD_UNOCCLUDE};
+                shellTransitions.registerRemote(f, wrap(mOccludeAnimationRunner));
+            }
+        } else {
+            RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+            if (sEnableRemoteKeyguardGoingAwayAnimation) {
+                final RemoteAnimationAdapter exitAnimationAdapter =
+                        new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
+                definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
+                        exitAnimationAdapter);
+                definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                        exitAnimationAdapter);
+            }
+            if (sEnableRemoteKeyguardOccludeAnimation) {
+                final RemoteAnimationAdapter occludeAnimationAdapter =
+                        new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
+                definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE,
+                        occludeAnimationAdapter);
+                definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE,
+                        occludeAnimationAdapter);
+            }
+            ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
+                    DEFAULT_DISPLAY, definition);
         }
-        if (sEnableRemoteKeyguardOccludeAnimation) {
-            final RemoteAnimationAdapter occludeAnimationAdapter =
-                    new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
-            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE, occludeAnimationAdapter);
-            definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, occludeAnimationAdapter);
-        }
-        ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
-                DEFAULT_DISPLAY, definition);
     }
 
     @Override
@@ -145,10 +288,10 @@
                 RemoteAnimationTarget[] wallpapers,
                 RemoteAnimationTarget[] nonApps,
                 IRemoteAnimationFinishedCallback finishedCallback) {
-            Trace.beginSection("KeyguardService.mBinder#startKeyguardExitAnimation");
+            Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
             checkPermission();
             mKeyguardViewMediator.startKeyguardExitAnimation(transit, apps, wallpapers,
-                    null /* nonApps */, finishedCallback);
+                    nonApps, finishedCallback);
             Trace.endSection();
         }
 
@@ -166,14 +309,14 @@
                        RemoteAnimationTarget[] wallpapers,
                         RemoteAnimationTarget[] nonApps,
                         IRemoteAnimationFinishedCallback finishedCallback) {
+            Slog.d(TAG, "mOccludeAnimationRunner.onAnimationStart: transit=" + transit);
             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.
+                // TODO(bc-unlock): Implement (un)occlude animation.
                 finishedCallback.onAnimationFinished();
             } catch (RemoteException e) {
                 Slog.e(TAG, "RemoteException");
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c7c2590..d12c763 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2369,7 +2369,7 @@
         final boolean wasShowing = mShowing;
         onKeyguardExitFinished();
 
-        if (mKeyguardStateController.isDismissingFromSwipe() || !wasShowing) {
+        if (mKeyguardStateController.isDismissingFromSwipe() || wasShowing) {
             mKeyguardUnlockAnimationControllerLazy.get().hideKeyguardViewAfterRemoteAnimation();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index fe2ac31..4a67e94 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -94,8 +94,8 @@
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.IWindowManager;
-import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -597,6 +597,7 @@
     }
 
     public void destroyView() {
+        setAutoHideController(/* autoHideController */ null);
         mCommandQueue.removeCallback(this);
         mContext.getSystemService(WindowManager.class).removeViewImmediate(
                 mNavigationBarView.getRootView());
@@ -919,6 +920,11 @@
 
     @Override
     public void onRotationProposal(final int rotation, boolean isValid) {
+        // The CommandQueue callbacks are added when the view is created to ensure we track other
+        // states, but until the view is attached (at the next traversal), the view's display is
+        // not valid.  Just ignore the rotation in this case.
+        if (!mNavigationBarView.isAttachedToWindow()) return;
+
         final int winRotation = mNavigationBarView.getDisplay().getRotation();
         final boolean rotateSuggestionsDisabled = RotationButtonController
                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
@@ -966,7 +972,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsState requestedState, String packageName) {
+            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
         if (displayId != mDisplayId) {
             return;
         }
@@ -1484,7 +1490,7 @@
     }
 
     /** Sets {@link AutoHideController} to the navigation bar. */
-    public void setAutoHideController(AutoHideController autoHideController) {
+    private void setAutoHideController(AutoHideController autoHideController) {
         mAutoHideController = autoHideController;
         if (mAutoHideController != null) {
             mAutoHideController.setNavigationBar(mAutoHideUiElement);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 458c50d..543004e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -402,7 +402,6 @@
     void removeNavigationBar(int displayId) {
         NavigationBar navBar = mNavigationBars.get(displayId);
         if (navBar != null) {
-            navBar.setAutoHideController(/* autoHideController */ null);
             navBar.destroyView();
             mNavigationBars.remove(displayId);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 808b7e2..2d36de3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -98,6 +98,7 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 public class NavigationBarView extends FrameLayout implements
@@ -354,6 +355,7 @@
         mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class)
                 .create(mContext);
         mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates);
+        Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
                     @Override
@@ -376,7 +378,7 @@
                     public boolean isSamplingEnabled() {
                         return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
                     }
-                });
+                }, backgroundExecutor);
 
         mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class);
         if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 1d44146..fe24ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -26,7 +26,7 @@
 
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import com.android.internal.view.AppearanceRegion;
 import com.android.systemui.model.SysUiState;
@@ -109,7 +109,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
-            InsetsState requestedState, String packageName) {
+            InsetsVisibilities requestedVisibilities, String packageName) {
         mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
index 7fdb79e..ea04cef 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/NavigationBarEdgePanel.java
@@ -58,6 +58,7 @@
 import com.android.systemui.statusbar.VibratorHelper;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 public class NavigationBarEdgePanel extends View implements NavigationEdgeBackPlugin {
 
@@ -349,6 +350,7 @@
                 .getDimension(R.dimen.navigation_edge_action_drag_threshold);
         setVisibility(GONE);
 
+        Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR);
         boolean isPrimaryDisplay = mContext.getDisplayId() == DEFAULT_DISPLAY;
         mRegionSamplingHelper = new RegionSamplingHelper(this,
                 new RegionSamplingHelper.SamplingCallback() {
@@ -366,7 +368,7 @@
                     public boolean isSamplingEnabled() {
                         return isPrimaryDisplay;
                     }
-                });
+                }, backgroundExecutor);
         mRegionSamplingHelper.setWindowVisible(true);
         mShowProtection = !isPrimaryDisplay;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
index 560d89a..c9a9399 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/RegionSamplingHelper.java
@@ -30,6 +30,7 @@
 import com.android.systemui.R;
 
 import java.io.PrintWriter;
+import java.util.concurrent.Executor;
 
 /**
  * A helper class to sample regions on the screen and inspect its luminosity.
@@ -52,6 +53,7 @@
      */
     private final Rect mRegisteredSamplingBounds = new Rect();
     private final SamplingCallback mCallback;
+    private final Executor mBackgroundExecutor;
     private boolean mSamplingEnabled = false;
     private boolean mSamplingListenerRegistered = false;
 
@@ -82,7 +84,9 @@
         }
     };
 
-    public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback) {
+    public RegionSamplingHelper(View sampledView, SamplingCallback samplingCallback,
+            Executor backgroundExecutor) {
+        mBackgroundExecutor = backgroundExecutor;
         mSamplingListener = new CompositionSamplingListener(
                 sampledView.getContext().getMainExecutor()) {
             @Override
@@ -183,10 +187,13 @@
                 // We only want to reregister if something actually changed
                 unregisterSamplingListener();
                 mSamplingListenerRegistered = true;
-                CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
-                        stopLayerControl, mSamplingRequestBounds);
+                SurfaceControl registeredStopLayer = stopLayerControl;
+                mBackgroundExecutor.execute(() -> {
+                    CompositionSamplingListener.register(mSamplingListener, DEFAULT_DISPLAY,
+                            registeredStopLayer, mSamplingRequestBounds);
+                });
                 mRegisteredSamplingBounds.set(mSamplingRequestBounds);
-                mRegisteredStopLayer = stopLayerControl;
+                mRegisteredStopLayer = registeredStopLayer;
             }
             mFirstSamplingAfterStart = false;
         } else {
@@ -199,7 +206,9 @@
             mSamplingListenerRegistered = false;
             mRegisteredStopLayer = null;
             mRegisteredSamplingBounds.setEmpty();
-            CompositionSamplingListener.unregister(mSamplingListener);
+            mBackgroundExecutor.execute(() -> {
+                CompositionSamplingListener.unregister(mSamplingListener);
+            });
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index a318073..4fcd46c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -423,6 +423,9 @@
             if (mQsPanelController.shouldUseHorizontalLayout()
                     && mQsPanelController.mMediaHost.hostView != null) {
                 builder.addFloat(mQsPanelController.mMediaHost.hostView, "alpha", 0, 1);
+            } else {
+                // In portrait, media view should always be visible
+                mQsPanelController.mMediaHost.hostView.setAlpha(1.0f);
             }
             mAllPagesDelayedAnimator = builder.build();
             mAllViews.add(mSecurityFooter.getView());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index f66b722..bc21b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -69,7 +69,7 @@
 
     private var hasControlsApps = AtomicBoolean(false)
 
-    private val icon = ResourceIcon.get(R.drawable.ic_device_light)
+    private val icon = ResourceIcon.get(R.drawable.controls_icon)
 
     private val listingCallback = object : ControlsListingController.ControlsListingCallback {
         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index a9ebcad..9e4a355 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -354,9 +354,10 @@
                 convertGammaToLinearFloat(value, minBacklight, maxBacklight),
                 maxBacklight);
         if (stopTracking) {
-            // TODO(brightnessfloat): change to use float value instead.
+            // Log brightness as a value between 0-1000 directly correlated to brightnesses 0-1.0
             MetricsLogger.action(mContext, metric,
-                    BrightnessSynchronizer.brightnessFloatToInt(valFloat));
+                    Math.round(MathUtils.constrainedMap(0, 1000, PowerManager.BRIGHTNESS_MIN,
+                        PowerManager.BRIGHTNESS_MAX, valFloat)));
 
         }
         setBrightness(valFloat);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index c7f8dcf..90158c32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -50,8 +50,8 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -338,7 +338,8 @@
          */
         default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsState requestedState, String packageName) { }
+                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                String packageName) { }
 
         /**
          * @see IStatusBar#showTransient(int, int[]).
@@ -997,7 +998,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsState requestedState, String packageName) {
+            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
             args.argi1 = displayId;
@@ -1005,7 +1006,7 @@
             args.argi3 = navbarColorManagedByIme ? 1 : 0;
             args.arg1 = appearanceRegions;
             args.argi4 = behavior;
-            args.arg2 = requestedState;
+            args.arg2 = requestedVisibilities;
             args.arg3 = packageName;
             mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
         }
@@ -1389,7 +1390,7 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
                                 (AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
-                                (InsetsState) args.arg2, (String) args.arg3);
+                                (InsetsVisibilities) args.arg2, (String) args.arg3);
                     }
                     args.recycle();
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 120121c..92922b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -77,6 +77,7 @@
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -100,7 +101,7 @@
     private static final boolean DEBUG_CHARGING_SPEED = false;
 
     private static final int MSG_HIDE_TRANSIENT = 1;
-    private static final int MSG_SWIPE_UP_TO_UNLOCK = 2;
+    private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2;
     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
     private static final float BOUNCE_ANIMATION_FINAL_Y = 0f;
 
@@ -121,6 +122,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final IActivityManager mIActivityManager;
     private final FalsingManager mFalsingManager;
+    private final KeyguardBypassController mKeyguardBypassController;
 
     protected KeyguardIndicationRotateTextViewController mRotateTextViewController;
     private BroadcastReceiver mBroadcastReceiver;
@@ -175,7 +177,8 @@
             @Main DelayableExecutor executor,
             FalsingManager falsingManager,
             LockPatternUtils lockPatternUtils,
-            IActivityManager iActivityManager) {
+            IActivityManager iActivityManager,
+            KeyguardBypassController keyguardBypassController) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mDevicePolicyManager = devicePolicyManager;
@@ -191,6 +194,7 @@
         mLockPatternUtils = lockPatternUtils;
         mIActivityManager = iActivityManager;
         mFalsingManager = falsingManager;
+        mKeyguardBypassController = keyguardBypassController;
 
     }
 
@@ -593,7 +597,7 @@
         mTransientIndication = transientIndication;
         mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
         mHandler.removeMessages(MSG_HIDE_TRANSIENT);
-        mHandler.removeMessages(MSG_SWIPE_UP_TO_UNLOCK);
+        mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK);
         if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
             // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
             mWakeLock.setAcquired(true);
@@ -785,27 +789,35 @@
         public void handleMessage(Message msg) {
             if (msg.what == MSG_HIDE_TRANSIENT) {
                 hideTransientIndication();
-            } else if (msg.what == MSG_SWIPE_UP_TO_UNLOCK) {
-                showSwipeUpToUnlock();
+            } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) {
+                showActionToUnlock();
             }
         }
     };
 
-    private void showSwipeUpToUnlock() {
+    /**
+     * Show message on the keyguard for how the user can unlock/enter their device.
+     */
+    public void showActionToUnlock() {
         if (mDozing) {
             return;
         }
 
         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
             if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
-                return; // udfps affordance is highlighted, no need to surface face auth error
-            } else {
+                return; // udfps affordance is highlighted, no need to show action to unlock
+            } else if (mKeyguardUpdateMonitor.isFaceEnrolled()) {
                 String message = mContext.getString(R.string.keyguard_retry);
                 mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
             }
         } else if (mKeyguardUpdateMonitor.isScreenOn()) {
-            showTransientIndication(mContext.getString(R.string.keyguard_unlock),
-                    false /* isError */, true /* hideOnScreenOff */);
+            if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
+                showTransientIndication(mContext.getString(R.string.keyguard_unlock_press),
+                        false /* isError */, true /* hideOnScreenOff */);
+            } else {
+                showTransientIndication(mContext.getString(R.string.keyguard_unlock),
+                        false /* isError */, true /* hideOnScreenOff */);
+            }
         }
     }
 
@@ -894,7 +906,7 @@
                 showTransientIndication(helpString, false /* isError */, showSwipeToUnlock);
             }
             if (showSwipeToUnlock) {
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SWIPE_UP_TO_UNLOCK),
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK),
                         TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
             }
         }
@@ -928,7 +940,7 @@
                     );
                 } else {
                     // suggest swiping up to unlock (try face auth again or swipe up to bouncer)
-                    showSwipeUpToUnlock();
+                    showActionToUnlock();
                 }
             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState);
@@ -1010,6 +1022,11 @@
                 boolean isStrongBiometric) {
             super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
             mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
+
+            if (biometricSourceType == BiometricSourceType.FACE
+                    && !mKeyguardBypassController.canBypass()) {
+                mHandler.sendEmptyMessage(MSG_SHOW_ACTION_TO_UNLOCK);
+            }
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index d4f5bd2..545dca8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -32,7 +32,7 @@
 import android.util.FloatProperty;
 import android.util.Log;
 import android.view.InsetsFlags;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.WindowInsetsController.Appearance;
@@ -434,9 +434,9 @@
 
     @Override
     public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsState requestedState, String packageName) {
-        boolean isFullscreen = !requestedState.getSourceOrDefaultVisibility(ITYPE_STATUS_BAR)
-                || !requestedState.getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR);
+            InsetsVisibilities requestedVisibilities, String packageName) {
+        boolean isFullscreen = !requestedVisibilities.getVisibility(ITYPE_STATUS_BAR)
+                || !requestedVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
         if (mIsFullscreen != isFullscreen) {
             mIsFullscreen = isFullscreen;
             synchronized (mListeners) {
@@ -451,7 +451,7 @@
         if (DEBUG_IMMERSIVE_APPS) {
             boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
             String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
-            String requestedVisibilityString = requestedState.toSourceVisibilityString();
+            String requestedVisibilityString = requestedVisibilities.toString();
             if (requestedVisibilityString.isEmpty()) {
                 requestedVisibilityString = "none";
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 0bbae2a..f0b2c2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,7 +19,7 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -161,7 +161,7 @@
      * Set the system bar attributes
      */
     void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsState requestedState, String packageName);
+            InsetsVisibilities requestedVisibilities, String packageName);
 
     /**
      * Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 5f10e55..29cfb07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -18,6 +18,8 @@
 
 import android.animation.Animator
 import android.annotation.UiThread
+import android.graphics.Point
+import android.graphics.Rect
 import android.util.Log
 import android.view.Gravity
 import android.view.View
@@ -31,9 +33,16 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
-import com.android.systemui.statusbar.phone.StatusBarLocationPublisher
-import com.android.systemui.statusbar.phone.StatusBarMarginUpdatedListener
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.leak.RotationUtils
+import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_NONE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE
+import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN
+import com.android.systemui.util.leak.RotationUtils.Rotation
 
 import java.lang.IllegalStateException
 import java.util.concurrent.Executor
@@ -58,7 +67,8 @@
 class PrivacyDotViewController @Inject constructor(
     @Main private val mainExecutor: Executor,
     private val stateController: StatusBarStateController,
-    private val locationPublisher: StatusBarLocationPublisher,
+    private val configurationController: ConfigurationController,
+    private val contentInsetsProvider: StatusBarContentInsetsProvider,
     private val animationScheduler: SystemStatusAnimationScheduler
 ) {
     private var sbHeightPortrait = 0
@@ -84,18 +94,27 @@
     // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread
     private var uiExecutor: DelayableExecutor? = null
 
-    private val marginListener: StatusBarMarginUpdatedListener =
-            object : StatusBarMarginUpdatedListener {
-        override fun onStatusBarMarginUpdated(marginLeft: Int, marginRight: Int) {
-            setStatusBarMargins(marginLeft, marginRight)
-        }
-    }
-
     private val views: Sequence<View>
         get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
 
     init {
-        locationPublisher.addCallback(marginListener)
+        contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
+            override fun onStatusBarContentInsetsChanged() {
+                dlog("onStatusBarContentInsetsChanged: ")
+                setNewLayoutRects()
+            }
+        })
+        configurationController.addCallback(object : ConfigurationController.ConfigurationListener {
+            override fun onLayoutDirectionChanged(isRtl: Boolean) {
+                synchronized(this) {
+                    val corner = selectDesignatedCorner(nextViewState.rotation, isRtl)
+                    nextViewState = nextViewState.copy(
+                            layoutRtl = isRtl,
+                            designatedCorner = corner
+                    )
+                }
+            }
+        })
 
         stateController.addCallback(object : StatusBarStateController.StateListener {
             override fun onExpandedChanged(isExpanded: Boolean) {
@@ -123,16 +142,19 @@
     fun setNewRotation(rot: Int) {
         dlog("updateRotation: $rot")
 
+        val isRtl: Boolean
         synchronized(lock) {
             if (rot == nextViewState.rotation) {
                 return
             }
+
+            isRtl = nextViewState.layoutRtl
         }
 
         // If we rotated, hide all dotes until the next state resolves
         setCornerVisibilities(View.INVISIBLE)
 
-        val newCorner = selectDesignatedCorner(rot)
+        val newCorner = selectDesignatedCorner(rot, isRtl)
         val index = newCorner.cornerIndex()
 
         val h = when (rot) {
@@ -222,15 +244,77 @@
         }
     }
 
+    @UiThread
+    private fun setCornerSizes(state: ViewState) {
+        // StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
+        // in every rotation. The only thing we need to check is rtl
+        val rtl = state.layoutRtl
+        val size = Point()
+        tl.context.display.getRealSize(size)
+        val currentRotation = RotationUtils.getExactRotation(tl.context)
+
+        val displayWidth: Int
+        val displayHeight: Int
+        if (currentRotation == ROTATION_LANDSCAPE || currentRotation == ROTATION_SEASCAPE) {
+            displayWidth = size.y
+            displayHeight = size.x
+        } else {
+            displayWidth = size.x
+            displayHeight = size.y
+        }
+
+        var rot = activeRotationForCorner(tl, rtl)
+        var contentInsets = state.contentRectForRotation(rot)
+        (tl.layoutParams as FrameLayout.LayoutParams).apply {
+            height = contentInsets.height()
+            if (rtl) {
+                width = contentInsets.left
+            } else {
+                width = displayHeight - contentInsets.right
+            }
+        }
+
+        rot = activeRotationForCorner(tr, rtl)
+        contentInsets = state.contentRectForRotation(rot)
+        (tr.layoutParams as FrameLayout.LayoutParams).apply {
+            height = contentInsets.height()
+            if (rtl) {
+                width = contentInsets.left
+            } else {
+                width = displayWidth - contentInsets.right
+            }
+        }
+
+        rot = activeRotationForCorner(br, rtl)
+        contentInsets = state.contentRectForRotation(rot)
+        (br.layoutParams as FrameLayout.LayoutParams).apply {
+            height = contentInsets.height()
+            if (rtl) {
+                width = contentInsets.left
+            } else {
+                width = displayHeight - contentInsets.right
+            }
+        }
+
+        rot = activeRotationForCorner(bl, rtl)
+        contentInsets = state.contentRectForRotation(rot)
+        (bl.layoutParams as FrameLayout.LayoutParams).apply {
+            height = contentInsets.height()
+            if (rtl) {
+                width = contentInsets.left
+            } else {
+                width = displayWidth - contentInsets.right
+            }
+        }
+    }
+
     // Designated view will be the one at statusbar's view.END
     @UiThread
-    private fun selectDesignatedCorner(r: Int): View? {
+    private fun selectDesignatedCorner(r: Int, isRtl: Boolean): View? {
         if (!this::tl.isInitialized) {
             return null
         }
 
-        val isRtl = tl.isLayoutRtl
-
         return when (r) {
             0 -> if (isRtl) tl else tr
             1 -> if (isRtl) tr else br
@@ -282,6 +366,17 @@
         return modded
     }
 
+    @Rotation
+    private fun activeRotationForCorner(corner: View, rtl: Boolean): Int {
+        // Each corner will only be visible in a single rotation, based on rtl
+        return when (corner) {
+            tr -> if (rtl) ROTATION_LANDSCAPE else ROTATION_NONE
+            tl -> if (rtl) ROTATION_NONE else ROTATION_SEASCAPE
+            br -> if (rtl) ROTATION_UPSIDE_DOWN else ROTATION_LANDSCAPE
+            else /* bl */ -> if (rtl) ROTATION_SEASCAPE else ROTATION_UPSIDE_DOWN
+        }
+    }
+
     private fun widthForCorner(corner: Int, left: Int, right: Int): Int {
         return when (corner) {
             TOP_LEFT, BOTTOM_LEFT -> left
@@ -303,15 +398,32 @@
         bl = bottomLeft
         br = bottomRight
 
-        val dc = selectDesignatedCorner(0)
+        val rtl = configurationController.isLayoutRtl
+        val dc = selectDesignatedCorner(0, rtl)
+
         val index = dc.cornerIndex()
 
         mainExecutor.execute {
             animationScheduler.addCallback(systemStatusAnimationCallback)
         }
 
+        val left = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_SEASCAPE)
+        val top = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_NONE)
+        val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE)
+        val bottom = contentInsetsProvider
+                .getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN)
+
         synchronized(lock) {
-            nextViewState = nextViewState.copy(designatedCorner = dc, cornerIndex = index)
+            nextViewState = nextViewState.copy(
+                    viewInitialized = true,
+                    designatedCorner = dc,
+                    cornerIndex = index,
+                    seascapeRect = left,
+                    portraitRect = top,
+                    landscapeRect = right,
+                    upsideDownRect = bottom,
+                    layoutRtl = rtl
+            )
         }
     }
 
@@ -324,19 +436,6 @@
         sbHeightLandscape = landscape
     }
 
-    /**
-     * The dot view containers will fill the margin in order to position the dots correctly
-     *
-     * @param left the space between the status bar contents and the left side of the screen
-     * @param right space between the status bar contents and the right side of the screen
-     */
-    private fun setStatusBarMargins(left: Int, right: Int) {
-        dlog("setStatusBarMargins l=$left r=$right")
-        synchronized(lock) {
-            nextViewState = nextViewState.copy(marginLeft = left, marginRight = right)
-        }
-    }
-
     private fun updateStatusBarState() {
         synchronized(lock) {
             nextViewState = nextViewState.copy(shadeExpanded = isShadeInQs())
@@ -377,6 +476,11 @@
     @UiThread
     private fun resolveState(state: ViewState) {
         dlog("resolveState $state")
+        if (!state.viewInitialized) {
+            dlog("resolveState: view is not initialized. skipping.")
+            return
+        }
+
         if (state == currentViewState) {
             dlog("resolveState: skipping")
             return
@@ -387,23 +491,15 @@
             updateRotations(state.rotation)
         }
 
-        if (state.height != currentViewState.height) {
-            updateHeights(state.rotation)
-        }
-
-        if (state.marginLeft != currentViewState.marginLeft ||
-                state.marginRight != currentViewState.marginRight) {
-            updateCornerSizes(state.marginLeft, state.marginRight, state.rotation)
+        if (state.needsLayout(currentViewState)) {
+            setCornerSizes(state)
+            views.forEach { it.requestLayout() }
         }
 
         if (state.designatedCorner != currentViewState.designatedCorner) {
             updateDesignatedCorner(state.designatedCorner, state.shouldShowDot())
         }
 
-        if (state.needsLayout(currentViewState)) {
-            views.forEach { it.requestLayout() }
-        }
-
         val shouldShow = state.shouldShowDot()
         if (shouldShow != currentViewState.shouldShowDot()) {
             if (shouldShow && state.designatedCorner != null) {
@@ -441,6 +537,30 @@
         }
         return -1
     }
+
+    // Returns [left, top, right, bottom] aka [seascape, none, landscape, upside-down]
+    private fun getLayoutRects(): List<Rect> {
+        val left = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_SEASCAPE)
+        val top = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_NONE)
+        val right = contentInsetsProvider.getStatusBarContentInsetsForRotation(ROTATION_LANDSCAPE)
+        val bottom = contentInsetsProvider
+                .getStatusBarContentInsetsForRotation(ROTATION_UPSIDE_DOWN)
+
+        return listOf(left, top, right, bottom)
+    }
+
+    private fun setNewLayoutRects() {
+        val rects = getLayoutRects()
+
+        synchronized(lock) {
+            nextViewState = nextViewState.copy(
+                    seascapeRect = rects[0],
+                    portraitRect = rects[1],
+                    landscapeRect = rects[2],
+                    upsideDownRect = rects[3]
+            )
+        }
+    }
 }
 
 private fun dlog(s: String) {
@@ -461,7 +581,7 @@
 const val BOTTOM_LEFT = 3
 private const val DURATION = 160L
 private const val TAG = "PrivacyDotViewController"
-private const val DEBUG = true
+private const val DEBUG = false
 private const val DEBUG_VERBOSE = false
 
 private fun Int.toGravity(): Int {
@@ -485,14 +605,20 @@
 }
 
 private data class ViewState(
+    val viewInitialized: Boolean = false,
+
     val systemPrivacyEventIsActive: Boolean = false,
     val shadeExpanded: Boolean = false,
     val qsExpanded: Boolean = false,
 
+    val portraitRect: Rect? = null,
+    val landscapeRect: Rect? = null,
+    val upsideDownRect: Rect? = null,
+    val seascapeRect: Rect? = null,
+    val layoutRtl: Boolean = false,
+
     val rotation: Int = 0,
     val height: Int = 0,
-    val marginLeft: Int = 0,
-    val marginRight: Int = 0,
     val cornerIndex: Int = -1,
     val designatedCorner: View? = null
 ) {
@@ -502,7 +628,20 @@
 
     fun needsLayout(other: ViewState): Boolean {
         return rotation != other.rotation ||
-                marginRight != other.marginRight ||
-                height != other.height
+                layoutRtl != other.layoutRtl ||
+                portraitRect != other.portraitRect ||
+                landscapeRect != other.landscapeRect ||
+                upsideDownRect != other.upsideDownRect ||
+                seascapeRect != other.seascapeRect
+    }
+
+    fun contentRectForRotation(@Rotation rot: Int): Rect {
+        return when (rot) {
+            ROTATION_NONE -> portraitRect!!
+            ROTATION_LANDSCAPE -> landscapeRect!!
+            ROTATION_UPSIDE_DOWN -> upsideDownRect!!
+            ROTATION_SEASCAPE -> seascapeRect!!
+            else -> throw IllegalArgumentException("not a rotation ($rot)")
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 93166f3..73bb6cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -287,7 +287,7 @@
                 mGroupExpansionChanging = true;
                 final boolean wasExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
                 boolean nowExpanded = mGroupExpansionManager.toggleGroupExpansion(mEntry);
-                mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
+                mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER,
                         nowExpanded);
                 onExpansionChanged(true /* userAction */, wasExpanded);
@@ -310,7 +310,7 @@
                     setUserExpanded(nowExpanded);
                 }
                 notifyHeightChanged(true);
-                mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded);
+                mOnExpandClickListener.onExpandClicked(mEntry, v, nowExpanded);
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_EXPANDER,
                         nowExpanded);
             }
@@ -3064,7 +3064,7 @@
     }
 
     public interface OnExpandClickListener {
-        void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded);
+        void onExpandClicked(NotificationEntry clickedEntry, View clickedView, boolean nowExpanded);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index a68dab9..00341b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -37,7 +37,6 @@
 import android.view.ViewStub;
 import android.widget.LinearLayout;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -79,9 +78,9 @@
     public static final int FADE_IN_DURATION = 320;
     public static final int FADE_IN_DELAY = 50;
     private PhoneStatusBarView mStatusBar;
-    private StatusBarStateController mStatusBarStateController;
-    private KeyguardStateController mKeyguardStateController;
-    private NetworkController mNetworkController;
+    private final StatusBarStateController mStatusBarStateController;
+    private final KeyguardStateController mKeyguardStateController;
+    private final NetworkController mNetworkController;
     private LinearLayout mSystemIconArea;
     private View mClockView;
     private View mOngoingCallChip;
@@ -92,12 +91,13 @@
     private Lazy<Optional<StatusBar>> mStatusBarOptionalLazy;
     private DarkIconManager mDarkIconManager;
     private View mOperatorNameFrame;
-    private CommandQueue mCommandQueue;
-    private OngoingCallController mOngoingCallController;
+    private final CommandQueue mCommandQueue;
+    private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
     private final StatusBarLocationPublisher mLocationPublisher;
-    private NotificationIconAreaController mNotificationIconAreaController;
     private final FeatureFlags mFeatureFlags;
+    private final NotificationIconAreaController mNotificationIconAreaController;
+    private final StatusBarIconController mStatusBarIconController;
 
     private List<String> mBlockedIcons = new ArrayList<>();
 
@@ -122,7 +122,12 @@
             StatusBarLocationPublisher locationPublisher,
             NotificationIconAreaController notificationIconAreaController,
             FeatureFlags featureFlags,
-            Lazy<Optional<StatusBar>> statusBarOptionalLazy
+            Lazy<Optional<StatusBar>> statusBarOptionalLazy,
+            StatusBarIconController statusBarIconController,
+            KeyguardStateController keyguardStateController,
+            NetworkController networkController,
+            StatusBarStateController statusBarStateController,
+            CommandQueue commandQueue
     ) {
         mOngoingCallController = ongoingCallController;
         mAnimationScheduler = animationScheduler;
@@ -130,15 +135,11 @@
         mNotificationIconAreaController = notificationIconAreaController;
         mFeatureFlags = featureFlags;
         mStatusBarOptionalLazy = statusBarOptionalLazy;
-    }
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        mKeyguardStateController = Dependency.get(KeyguardStateController.class);
-        mNetworkController = Dependency.get(NetworkController.class);
-        mStatusBarStateController = Dependency.get(StatusBarStateController.class);
-        mCommandQueue = Dependency.get(CommandQueue.class);
+        mStatusBarIconController = statusBarIconController;
+        mKeyguardStateController = keyguardStateController;
+        mNetworkController = networkController;
+        mStatusBarStateController = statusBarStateController;
+        mCommandQueue = commandQueue;
     }
 
     @Override
@@ -164,7 +165,7 @@
         mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_alarm_clock));
         mBlockedIcons.add(getString(com.android.internal.R.string.status_bar_call_strength));
         mDarkIconManager.setBlockList(mBlockedIcons);
-        Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager);
+        mStatusBarIconController.addIconGroup(mDarkIconManager);
         mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
         mClockView = mStatusBar.findViewById(R.id.clock);
         mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
@@ -203,7 +204,8 @@
     @Override
     public void onDestroyView() {
         super.onDestroyView();
-        Dependency.get(StatusBarIconController.class).removeIconGroup(mDarkIconManager);
+        mStatusBarIconController.removeIconGroup(mDarkIconManager);
+        mAnimationScheduler.removeCallback(this);
         if (mNetworkController.hasEmergencyCryptKeeperText()) {
             mNetworkController.removeCallback(mSignalCallback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index b148eeb..07618da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -18,6 +18,7 @@
 import android.content.pm.ActivityInfo
 import android.content.res.Configuration
 import android.os.LocaleList
+import android.view.View.LAYOUT_DIRECTION_RTL
 import com.android.systemui.statusbar.policy.ConfigurationController
 
 import java.util.ArrayList
@@ -33,6 +34,7 @@
     private var uiMode: Int = 0
     private var localeList: LocaleList? = null
     private val context: Context
+    private var layoutDirection: Int
 
     init {
         val currentConfig = context.resources.configuration
@@ -44,6 +46,7 @@
                 Configuration.UI_MODE_TYPE_CAR
         uiMode = currentConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
         localeList = currentConfig.locales
+        layoutDirection = currentConfig.layoutDirection
     }
 
     override fun notifyThemeChanged() {
@@ -101,6 +104,13 @@
             }
         }
 
+        if (layoutDirection != newConfig.layoutDirection) {
+            layoutDirection = newConfig.layoutDirection
+            listeners.filterForEach({ this.listeners.contains(it) }) {
+                it.onLayoutDirectionChanged(layoutDirection == LAYOUT_DIRECTION_RTL)
+            }
+        }
+
         if (lastConfig.updateFrom(newConfig) and ActivityInfo.CONFIG_ASSETS_PATHS != 0) {
             listeners.filterForEach({ this.listeners.contains(it) }) {
                 it.onOverlayChanged()
@@ -116,6 +126,10 @@
     override fun removeCallback(listener: ConfigurationController.ConfigurationListener) {
         listeners.remove(listener)
     }
+
+    override fun isLayoutRtl(): Boolean {
+        return layoutDirection == LAYOUT_DIRECTION_RTL
+    }
 }
 
 // This could be done with a Collection.filter and Collection.forEach, but Collection.filter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 91d503b..0a4e59c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -19,6 +19,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 
+import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
 import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
@@ -40,6 +41,7 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
@@ -81,6 +83,11 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.controls.ControlsServiceInfo;
+import com.android.systemui.controls.dagger.ControlsComponent;
+import com.android.systemui.controls.management.ControlsListingController;
+import com.android.systemui.controls.ui.ControlsActivity;
+import com.android.systemui.controls.ui.ControlsUiController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.IntentButtonProvider;
@@ -98,6 +105,8 @@
 import com.android.systemui.wallet.controller.QuickAccessWalletController;
 import com.android.systemui.wallet.ui.WalletActivity;
 
+import java.util.List;
+
 /**
  * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
  * text.
@@ -133,9 +142,12 @@
     private KeyguardAffordanceView mLeftAffordanceView;
 
     private ImageView mWalletButton;
+    private ImageView mControlsButton;
     private boolean mHasCard = false;
     private WalletCardRetriever mCardRetriever = new WalletCardRetriever();
     private QuickAccessWalletController mQuickAccessWalletController;
+    private ControlsComponent mControlsComponent;
+    private boolean mControlServicesAvailable = false;
 
     private ViewGroup mIndicationArea;
     private TextView mIndicationText;
@@ -188,6 +200,19 @@
     private ActivityIntentHelper mActivityIntentHelper;
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
+    private ControlsListingController.ControlsListingCallback mListingCallback =
+            new ControlsListingController.ControlsListingCallback() {
+                public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
+                    boolean available = !serviceInfos.isEmpty();
+
+                    if (available != mControlServicesAvailable) {
+                        mControlServicesAvailable = available;
+                        updateControlsVisibility();
+                        updateAffordanceColors();
+                    }
+                }
+            };
+
     public KeyguardBottomAreaView(Context context) {
         this(context, null);
     }
@@ -253,6 +278,7 @@
         mRightAffordanceView = findViewById(R.id.camera_button);
         mLeftAffordanceView = findViewById(R.id.left_button);
         mWalletButton = findViewById(R.id.wallet_button);
+        mControlsButton = findViewById(R.id.controls_button);
         mIndicationArea = findViewById(R.id.keyguard_indication_area);
         mIndicationText = findViewById(R.id.keyguard_indication_text);
         mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom);
@@ -276,6 +302,7 @@
         mIndicationPadding = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_area_padding);
         updateWalletVisibility();
+        updateControlsVisibility();
     }
 
     /**
@@ -328,6 +355,11 @@
             mQuickAccessWalletController.unregisterWalletChangeObservers(
                     WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
         }
+
+        if (mControlsComponent != null) {
+            mControlsComponent.getControlsListingController().ifPresent(
+                    c -> c.removeCallback(mListingCallback));
+        }
     }
 
     private void initAccessibility() {
@@ -369,13 +401,20 @@
         updateLeftAffordanceIcon();
 
         lp = mWalletButton.getLayoutParams();
-        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_wallet_width);
-        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_wallet_width);
+        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
+        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
         mWalletButton.setLayoutParams(lp);
 
+        lp = mControlsButton.getLayoutParams();
+        lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
+        lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
+        mControlsButton.setLayoutParams(lp);
+
         mIndicationPadding = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_area_padding);
+
         updateWalletVisibility();
+        updateAffordanceColors();
     }
 
     private void updateRightAffordanceIcon() {
@@ -454,22 +493,38 @@
                 || !mQuickAccessWalletController.isWalletEnabled()
                 || !mHasCard) {
             mWalletButton.setVisibility(GONE);
-            mIndicationArea.setPadding(0, 0, 0, 0);
-        } else {
-            Drawable tileIcon = mQuickAccessWalletController.getWalletClient().getTileIcon();
-            if (tileIcon != null) {
-                mWalletButton.setImageDrawable(tileIcon);
+
+            if (mControlsButton.getVisibility() == GONE) {
+                mIndicationArea.setPadding(0, 0, 0, 0);
             }
-            mWalletButton.getDrawable().setTint(
-                    Utils.getColorAttr(
-                            mContext,
-                            com.android.internal.R.attr.textColorPrimary).getDefaultColor());
+        } else {
             mWalletButton.setVisibility(VISIBLE);
             mWalletButton.setOnClickListener(this::onWalletClick);
             mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
         }
     }
 
+    private void updateControlsVisibility() {
+        if (mControlsComponent == null) return;
+
+        boolean hasFavorites = mControlsComponent.getControlsController()
+                .map(c -> c.getFavorites().size() > 0)
+                .orElse(false);
+        if (mDozing
+                || !hasFavorites
+                || !mControlServicesAvailable
+                || mControlsComponent.getVisibility() != AVAILABLE) {
+            mControlsButton.setVisibility(GONE);
+            if (mWalletButton.getVisibility() == GONE) {
+                mIndicationArea.setPadding(0, 0, 0, 0);
+            }
+        } else {
+            mControlsButton.setVisibility(VISIBLE);
+            mControlsButton.setOnClickListener(this::onControlsClick);
+            mIndicationArea.setPadding(mIndicationPadding, 0, mIndicationPadding, 0);
+        }
+    }
+
     public boolean isLeftVoiceAssist() {
         return mLeftIsVoiceAssist;
     }
@@ -751,6 +806,9 @@
         if (mWalletButton.getVisibility() == View.VISIBLE) {
             startFinishDozeAnimationElement(mWalletButton, delay);
         }
+        if (mControlsButton.getVisibility() == View.VISIBLE) {
+            startFinishDozeAnimationElement(mControlsButton, delay);
+        }
         if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
             startFinishDozeAnimationElement(mLeftAffordanceView, delay);
             delay += DOZE_ANIMATION_STAGGER_DELAY;
@@ -824,6 +882,7 @@
         updateCameraVisibility();
         updateLeftAffordanceIcon();
         updateWalletVisibility();
+        updateControlsVisibility();
 
         if (dozing) {
             mOverlayContainer.setVisibility(INVISIBLE);
@@ -857,6 +916,7 @@
         mRightAffordanceView.setAlpha(alpha);
         mIndicationArea.setAlpha(alpha);
         mWalletButton.setAlpha(alpha);
+        mControlsButton.setAlpha(alpha);
     }
 
     private class DefaultLeftButton implements IntentButton {
@@ -950,6 +1010,32 @@
         mQuickAccessWalletController.queryWalletCards(mCardRetriever);
 
         updateWalletVisibility();
+        updateAffordanceColors();
+    }
+
+    private void updateAffordanceColors() {
+        int iconColor = Utils.getColorAttrDefaultColor(
+                mContext,
+                com.android.internal.R.attr.textColorPrimary);
+        mWalletButton.getDrawable().setTint(iconColor);
+        mControlsButton.getDrawable().setTint(iconColor);
+
+        ColorStateList bgColor = Utils.getColorAttr(
+                mContext,
+                com.android.internal.R.attr.colorSurface);
+        mWalletButton.setBackgroundTintList(bgColor);
+        mControlsButton.setBackgroundTintList(bgColor);
+    }
+
+    /**
+      * Initialize controls via the ControlsComponent
+      */
+    public void initControls(ControlsComponent controlsComponent) {
+        mControlsComponent = controlsComponent;
+        mControlsComponent.getControlsListingController().ifPresent(
+                c -> c.addCallback(mListingCallback));
+
+        updateAffordanceColors();
     }
 
     private void onWalletClick(View v) {
@@ -974,19 +1060,41 @@
         }
     }
 
+    private void onControlsClick(View v) {
+        if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+            return;
+        }
+
+        Intent intent = new Intent(mContext, ControlsActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(ControlsUiController.EXTRA_ANIMATE, true);
+
+        if (mControlsComponent.getVisibility() == AVAILABLE) {
+            mContext.startActivity(intent);
+        } else {
+            mActivityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */);
+        }
+    }
+
     private class WalletCardRetriever implements
             QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
 
         @Override
         public void onWalletCardsRetrieved(@NonNull GetWalletCardsResponse response) {
             mHasCard = !response.getWalletCards().isEmpty();
+            Drawable tileIcon = mQuickAccessWalletController.getWalletClient().getTileIcon();
+            if (tileIcon != null) {
+                mWalletButton.setImageDrawable(tileIcon);
+            }
             updateWalletVisibility();
+            updateAffordanceColors();
         }
 
         @Override
         public void onWalletCardRetrievalError(@NonNull GetWalletCardsError error) {
             mHasCard = false;
             updateWalletVisibility();
+            updateAffordanceColors();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e272d27..222ed63 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -18,10 +18,7 @@
 
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
-import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
 
-import android.animation.ValueAnimator;
 import android.annotation.ColorInt;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -46,35 +43,17 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
-import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * The header group on Keyguard.
  */
-public class KeyguardStatusBarView extends RelativeLayout implements
-        BatteryStateChangeCallback,
-        OnUserInfoChangedListener,
-        ConfigurationListener,
-        SystemStatusAnimationCallback {
+public class KeyguardStatusBarView extends RelativeLayout {
 
     private static final int LAYOUT_NONE = 0;
     private static final int LAYOUT_CUTOUT = 1;
@@ -84,30 +63,23 @@
 
     private boolean mShowPercentAvailable;
     private boolean mBatteryCharging;
-    private boolean mBatteryListening;
 
     private TextView mCarrierLabel;
     private ImageView mMultiUserAvatar;
     private BatteryMeterView mBatteryView;
     private StatusIconContainer mStatusIconContainer;
 
-    private BatteryController mBatteryController;
     private boolean mKeyguardUserSwitcherEnabled;
     private final UserManager mUserManager;
 
     private int mSystemIconsSwitcherHiddenExpandedMargin;
     private int mSystemIconsBaseMargin;
     private View mSystemIconsContainer;
-    private TintedIconManager mIconManager;
-    private List<String> mBlockedIcons = new ArrayList<>();
 
     private View mCutoutSpace;
     private ViewGroup mStatusIconArea;
     private int mLayoutState = LAYOUT_NONE;
 
-    private SystemStatusAnimationScheduler mAnimationScheduler;
-    private FeatureFlags mFeatureFlags;
-
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
      */
@@ -141,10 +113,6 @@
         mStatusIconContainer = findViewById(R.id.statusIcons);
 
         loadDimens();
-        loadBlockList();
-        mBatteryController = Dependency.get(BatteryController.class);
-        mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class);
-        mFeatureFlags = Dependency.get(FeatureFlags.class);
     }
 
     @Override
@@ -190,7 +158,7 @@
         setLayoutParams(lp);
     }
 
-    private void loadDimens() {
+    void loadDimens() {
         Resources res = getResources();
         mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
                 R.dimen.system_icons_switcher_hidden_expanded_margin);
@@ -204,14 +172,6 @@
                 R.dimen.rounded_corner_content_padding);
     }
 
-    // Set hidden status bar items
-    private void loadBlockList() {
-        Resources r = getResources();
-        mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_volume));
-        mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_alarm_clock));
-        mBlockedIcons.add(r.getString(com.android.internal.R.string.status_bar_call_strength));
-    }
-
     private void updateVisibilities() {
         if (mMultiUserAvatar.getParent() != mStatusIconArea
                 && !mKeyguardUserSwitcherEnabled) {
@@ -348,59 +308,19 @@
         return true;
     }
 
-    public void setListening(boolean listening) {
-        if (listening == mBatteryListening) {
-            return;
-        }
-        mBatteryListening = listening;
-        if (mBatteryListening) {
-            mBatteryController.addCallback(this);
-        } else {
-            mBatteryController.removeCallback(this);
-        }
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        UserInfoController userInfoController = Dependency.get(UserInfoController.class);
-        userInfoController.addCallback(this);
-        userInfoController.reloadUserInfo();
-        Dependency.get(ConfigurationController.class).addCallback(this);
-        mIconManager = new TintedIconManager(findViewById(R.id.statusIcons), mFeatureFlags);
-        mIconManager.setBlockList(mBlockedIcons);
-        Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
-        mAnimationScheduler.addCallback(this);
-        onThemeChanged();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        Dependency.get(UserInfoController.class).removeCallback(this);
-        Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
-        Dependency.get(ConfigurationController.class).removeCallback(this);
-        mAnimationScheduler.removeCallback(this);
-    }
-
-    @Override
-    public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+    /** Should only be called from {@link KeyguardStatusBarViewController}. */
+    void onUserInfoChanged(Drawable picture) {
         mMultiUserAvatar.setImageDrawable(picture);
     }
 
-    @Override
-    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+    /** Should only be called from {@link KeyguardStatusBarViewController}. */
+    void onBatteryLevelChanged(boolean charging) {
         if (mBatteryCharging != charging) {
             mBatteryCharging = charging;
             updateVisibilities();
         }
     }
 
-    @Override
-    public void onPowerSaveChanged(boolean isPowerSave) {
-        // could not care less
-    }
-
     public void setKeyguardUserSwitcherEnabled(boolean enabled) {
         mKeyguardUserSwitcherEnabled = enabled;
     }
@@ -467,28 +387,20 @@
         return false;
     }
 
-    public void onThemeChanged() {
+    /** Should only be called from {@link KeyguardStatusBarViewController}. */
+    void onThemeChanged(StatusBarIconController.TintedIconManager iconManager) {
         mBatteryView.setColorsFromContext(mContext);
-        updateIconsAndTextColors();
-        // Reload user avatar
-        ((UserInfoControllerImpl) Dependency.get(UserInfoController.class))
-                .onDensityOrFontScaleChanged();
+        updateIconsAndTextColors(iconManager);
     }
 
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        loadDimens();
-    }
-
-    @Override
-    public void onOverlayChanged() {
+    /** Should only be called from {@link KeyguardStatusBarViewController}. */
+    void onOverlayChanged() {
         mCarrierLabel.setTextAppearance(
                 Utils.getThemeAttr(mContext, com.android.internal.R.attr.textAppearanceSmall));
-        onThemeChanged();
         mBatteryView.updatePercentView();
     }
 
-    private void updateIconsAndTextColors() {
+    private void updateIconsAndTextColors(StatusBarIconController.TintedIconManager iconManager) {
         @ColorInt int textColor = Utils.getColorAttrDefaultColor(mContext,
                 R.attr.wallpaperTextColor);
         @ColorInt int iconColor = Utils.getColorStateListDefaultColor(mContext,
@@ -496,8 +408,8 @@
                 R.color.light_mode_icon_color_single_tone);
         float intensity = textColor == Color.WHITE ? 0 : 1;
         mCarrierLabel.setTextColor(iconColor);
-        if (mIconManager != null) {
-            mIconManager.setTint(iconColor);
+        if (iconManager != null) {
+            iconManager.setTint(iconColor);
         }
 
         applyDarkness(R.id.battery, mEmptyRect, intensity, iconColor);
@@ -522,10 +434,10 @@
         }
     }
 
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+    /** Should only be called from {@link KeyguardStatusBarViewController}. */
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusBarView:");
         pw.println("  mBatteryCharging: " + mBatteryCharging);
-        pw.println("  mBatteryListening: " + mBatteryListening);
         pw.println("  mLayoutState: " + mLayoutState);
         pw.println("  mKeyguardUserSwitcherEnabled: " + mKeyguardUserSwitcherEnabled);
         if (mBatteryView != null) {
@@ -533,19 +445,16 @@
         }
     }
 
-    /** SystemStatusAnimationCallback */
-    @Override
-    public void onSystemChromeAnimationStart() {
-        if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT) {
+    void onSystemChromeAnimationStart(boolean isAnimatingOut) {
+        if (isAnimatingOut) {
             mSystemIconsContainer.setVisibility(View.VISIBLE);
             mSystemIconsContainer.setAlpha(0f);
         }
     }
 
-    @Override
-    public void onSystemChromeAnimationEnd() {
+    void onSystemChromeAnimationEnd(boolean isAnimatingIn) {
         // Make sure the system icons are out of the way
-        if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) {
+        if (isAnimatingIn) {
             mSystemIconsContainer.setVisibility(View.INVISIBLE);
             mSystemIconsContainer.setAlpha(0f);
         } else {
@@ -554,9 +463,8 @@
         }
     }
 
-    @Override
-    public void onSystemChromeAnimationUpdate(ValueAnimator anim) {
-        mSystemIconsContainer.setAlpha((float) anim.getAnimatedValue());
+    void onSystemChromeAnimationUpdate(float animatedValue) {
+        mSystemIconsContainer.setAlpha(animatedValue);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index 377fb92..5d8d36e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -16,20 +16,120 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN;
+import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT;
+
+import android.animation.ValueAnimator;
+import android.content.res.Resources;
+
+import androidx.annotation.NonNull;
+
 import com.android.keyguard.CarrierTextController;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.util.ViewController;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
 import javax.inject.Inject;
 
 /** View Controller for {@link com.android.systemui.statusbar.phone.KeyguardStatusBarView}. */
 public class KeyguardStatusBarViewController extends ViewController<KeyguardStatusBarView> {
     private final CarrierTextController mCarrierTextController;
+    private final ConfigurationController mConfigurationController;
+    private final SystemStatusAnimationScheduler mAnimationScheduler;
+    private final BatteryController mBatteryController;
+    private final UserInfoController mUserInfoController;
+    private final StatusBarIconController mStatusBarIconController;
+    private final StatusBarIconController.TintedIconManager.Factory mTintedIconManagerFactory;
+
+    private final ConfigurationController.ConfigurationListener mConfigurationListener =
+            new ConfigurationController.ConfigurationListener() {
+                @Override
+                public void onDensityOrFontScaleChanged() {
+                    mView.loadDimens();
+                }
+
+                @Override
+                public void onOverlayChanged() {
+                    KeyguardStatusBarViewController.this.onThemeChanged();
+                    mView.onOverlayChanged();
+                }
+
+                @Override
+                public void onThemeChanged() {
+                    KeyguardStatusBarViewController.this.onThemeChanged();
+                }
+            };
+
+    private final SystemStatusAnimationCallback mAnimationCallback =
+            new SystemStatusAnimationCallback() {
+                @Override
+                public void onSystemChromeAnimationStart() {
+                    mView.onSystemChromeAnimationStart(
+                            mAnimationScheduler.getAnimationState() == ANIMATING_OUT);
+                }
+
+                @Override
+                public void onSystemChromeAnimationEnd() {
+                    mView.onSystemChromeAnimationEnd(
+                            mAnimationScheduler.getAnimationState() == ANIMATING_IN);
+                }
+
+                @Override
+                public void onSystemChromeAnimationUpdate(@NonNull ValueAnimator anim) {
+                    mView.onSystemChromeAnimationUpdate((float) anim.getAnimatedValue());
+                }
+            };
+
+    private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
+            new BatteryController.BatteryStateChangeCallback() {
+                @Override
+                public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+                    mView.onBatteryLevelChanged(charging);
+                }
+            };
+
+    private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
+            (name, picture, userAccount) -> mView.onUserInfoChanged(picture);
+
+    private final List<String> mBlockedIcons;
+
+    private boolean mBatteryListening;
+    private StatusBarIconController.TintedIconManager mTintedIconManager;
 
     @Inject
     public KeyguardStatusBarViewController(
-            KeyguardStatusBarView view, CarrierTextController carrierTextController) {
+            KeyguardStatusBarView view,
+            CarrierTextController carrierTextController,
+            ConfigurationController configurationController,
+            SystemStatusAnimationScheduler animationScheduler,
+            BatteryController batteryController,
+            UserInfoController userInfoController,
+            StatusBarIconController statusBarIconController,
+            StatusBarIconController.TintedIconManager.Factory tintedIconManagerFactory) {
         super(view);
         mCarrierTextController = carrierTextController;
+        mConfigurationController = configurationController;
+        mAnimationScheduler = animationScheduler;
+        mBatteryController = batteryController;
+        mUserInfoController = userInfoController;
+        mStatusBarIconController = statusBarIconController;
+        mTintedIconManagerFactory = tintedIconManagerFactory;
+
+        Resources r = getResources();
+        mBlockedIcons = Collections.unmodifiableList(Arrays.asList(
+                r.getString(com.android.internal.R.string.status_bar_volume),
+                r.getString(com.android.internal.R.string.status_bar_alarm_clock),
+                r.getString(com.android.internal.R.string.status_bar_call_strength)));
     }
 
     @Override
@@ -40,9 +140,55 @@
 
     @Override
     protected void onViewAttached() {
+        mConfigurationController.addCallback(mConfigurationListener);
+        mAnimationScheduler.addCallback(mAnimationCallback);
+        mUserInfoController.addCallback(mOnUserInfoChangedListener);
+        if (mTintedIconManager == null) {
+            mTintedIconManager =
+                    mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
+            mTintedIconManager.setBlockList(mBlockedIcons);
+            mStatusBarIconController.addIconGroup(mTintedIconManager);
+        }
+        onThemeChanged();
     }
 
     @Override
     protected void onViewDetached() {
+        // Don't receive future #onViewAttached calls so that we don't accidentally have two
+        // controllers registered for the same view.
+        // TODO(b/194181195): This shouldn't be necessary.
+        destroy();
+
+        mConfigurationController.removeCallback(mConfigurationListener);
+        mAnimationScheduler.removeCallback(mAnimationCallback);
+        mUserInfoController.removeCallback(mOnUserInfoChangedListener);
+        if (mTintedIconManager != null) {
+            mStatusBarIconController.removeIconGroup(mTintedIconManager);
+        }
+    }
+
+    /** Should be called when the theme changes. */
+    public void onThemeChanged() {
+        mView.onThemeChanged(mTintedIconManager);
+    }
+
+    /** Sets whether this controller should listen to battery updates. */
+    public void setBatteryListening(boolean listening) {
+        if (listening == mBatteryListening) {
+            return;
+        }
+        mBatteryListening = listening;
+        if (mBatteryListening) {
+            mBatteryController.addCallback(mBatteryStateChangeCallback);
+        } else {
+            mBatteryController.removeCallback(mBatteryStateChangeCallback);
+        }
+    }
+
+    /** */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("KeyguardStatusBarView:");
+        pw.println("  mBatteryListening: " + mBatteryListening);
+        mView.dump(fd, pw, args);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index c213707..3f33281 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -21,7 +21,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.Nullable;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
@@ -150,7 +150,8 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsState requestedState, String packageName) {
+                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                String packageName) {
             if (displayId != mDisplayId) {
                 return;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 1e918b4..b8995f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -104,6 +104,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -326,6 +327,7 @@
     private final ScrimController mScrimController;
     private final PrivacyDotViewController mPrivacyDotViewController;
     private final QuickAccessWalletController mQuickAccessWalletController;
+    private final ControlsComponent mControlsComponent;
     private final NotificationRemoteInputManager mRemoteInputManager;
 
     // Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow card.
@@ -344,7 +346,7 @@
     private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
-    private KeyguardStatusBarViewController mKeyguarStatusBarViewController;
+    private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
     private ViewGroup mBigClockContainer;
     @VisibleForTesting QS mQs;
     private FrameLayout mQsFrame;
@@ -730,7 +732,8 @@
             SecureSettings secureSettings,
             SplitShadeHeaderController splitShadeHeaderController,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
-            NotificationRemoteInputManager remoteInputManager) {
+            NotificationRemoteInputManager remoteInputManager,
+            ControlsComponent controlsComponent) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 statusBarKeyguardViewManager, latencyTracker, flingAnimationUtilsBuilder.get(),
@@ -740,6 +743,7 @@
         mKeyguardMediaController = keyguardMediaController;
         mPrivacyDotViewController = privacyDotViewController;
         mQuickAccessWalletController = quickAccessWalletController;
+        mControlsComponent = controlsComponent;
         mMetricsLogger = metricsLogger;
         mActivityManager = activityManager;
         mConfigurationController = configurationController;
@@ -971,9 +975,13 @@
 
         KeyguardStatusBarViewComponent statusBarViewComponent =
                 mKeyguardStatusBarViewComponentFactory.build(keyguardStatusBarView);
-        mKeyguarStatusBarViewController =
+        if (mKeyguardStatusBarViewController != null) {
+            // TODO(b/194181195): This shouldn't be necessary.
+            mKeyguardStatusBarViewController.onViewDetached();
+        }
+        mKeyguardStatusBarViewController =
                 statusBarViewComponent.getKeyguardStatusBarViewController();
-        mKeyguarStatusBarViewController.init();
+        mKeyguardStatusBarViewController.init();
 
         if (mKeyguardUserSwitcherController != null) {
             // Try to close the switcher so that callbacks are triggered if necessary.
@@ -1150,10 +1158,6 @@
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
-        if (mKeyguardStatusBar != null) {
-            mKeyguardStatusBar.onThemeChanged();
-        }
-
         mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
                 mBarState,
                 false,
@@ -1188,6 +1192,7 @@
         mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
         mKeyguardBottomArea.setFalsingManager(mFalsingManager);
         mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
+        mKeyguardBottomArea.initControls(mControlsComponent);
     }
 
     private void updateMaxDisplayedNotifications(boolean recompute) {
@@ -3132,7 +3137,7 @@
     }
 
     private void setListening(boolean listening) {
-        mKeyguardStatusBar.setListening(listening);
+        mKeyguardStatusBarViewController.setBatteryListening(listening);
         if (mQs == null) return;
         mQs.setListening(listening);
     }
@@ -3683,8 +3688,6 @@
     public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
         if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
             mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
-            mLockIconViewController.setAmbientIndicationBottomPadding(
-                    mAmbientIndicationBottomPadding);
             updateMaxDisplayedNotifications(true);
         }
     }
@@ -3778,8 +3781,8 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         super.dump(fd, pw, args);
         pw.println("    gestureExclusionRect: " + calculateGestureExclusionRect());
-        if (mKeyguardStatusBar != null) {
-            mKeyguardStatusBar.dump(fd, pw, args);
+        if (mKeyguardStatusBarViewController != null) {
+            mKeyguardStatusBarViewController.dump(fd, pw, args);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 31a432e..c300b11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.EventLog;
@@ -52,6 +53,7 @@
     private static final boolean DEBUG = StatusBar.DEBUG;
     private static final boolean DEBUG_GESTURES = false;
     private final CommandQueue mCommandQueue;
+    private final StatusBarContentInsetsProvider mContentInsetsProvider;
 
     StatusBar mBar;
 
@@ -85,11 +87,10 @@
     private int mCutoutSideNudge = 0;
     private boolean mHeadsUpVisible;
 
-    private int mRoundedCornerPadding = 0;
-
     public PhoneStatusBarView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mCommandQueue = Dependency.get(CommandQueue.class);
+        mContentInsetsProvider = Dependency.get(StatusBarContentInsetsProvider.class);
     }
 
     public void setBar(StatusBar bar) {
@@ -305,8 +306,6 @@
     public void updateResources() {
         mCutoutSideNudge = getResources().getDimensionPixelSize(
                 R.dimen.display_cutout_margin_consumption);
-        mRoundedCornerPadding = getResources().getDimensionPixelSize(
-                R.dimen.rounded_corner_content_padding);
 
         updateStatusBarHeight();
     }
@@ -341,8 +340,7 @@
     private void updateLayoutForCutout() {
         updateStatusBarHeight();
         updateCutoutLocation(StatusBarWindowView.cornerCutoutMargins(mDisplayCutout, getDisplay()));
-        updateSafeInsets(StatusBarWindowView.statusBarCornerCutoutMargins(mDisplayCutout,
-                getDisplay(), mRotationOrientation, mStatusBarHeight));
+        updateSafeInsets();
     }
 
     private void updateCutoutLocation(Pair<Integer, Integer> cornerCutoutMargins) {
@@ -370,15 +368,18 @@
         lp.height = bounds.height();
     }
 
-    private void updateSafeInsets(Pair<Integer, Integer> cornerCutoutMargins) {
-        // Depending on our rotation, we may have to work around a cutout in the middle of the view,
-        // or letterboxing from the right or left sides.
+    private void updateSafeInsets() {
+        Rect contentRect = mContentInsetsProvider
+                .getStatusBarContentInsetsForRotation(RotationUtils.getExactRotation(getContext()));
 
-        Pair<Integer, Integer> padding =
-                StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
-                        mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
+        Point size = new Point();
+        getDisplay().getRealSize(size);
 
-        setPadding(padding.first, getPaddingTop(), padding.second, getPaddingBottom());
+        setPadding(
+                contentRect.left,
+                getPaddingTop(),
+                size.x - contentRect.right,
+                getPaddingBottom());
     }
 
     public void setHeadsUpVisible(boolean headsUpVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 10d5d57..3b5d69d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -39,9 +39,7 @@
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
 import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
 import static com.android.wm.shell.bubbles.BubbleController.TASKBAR_CHANGED_BROADCAST;
 
@@ -105,8 +103,8 @@
 import android.view.Display;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowManager;
-import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationAdapter;
@@ -158,8 +156,6 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.qualifiers.UiBackground;
-import com.android.systemui.demomode.DemoMode;
-import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.emergency.EmergencyGesture;
 import com.android.systemui.fragments.ExtensionFragmentListener;
@@ -187,7 +183,6 @@
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.settings.brightness.BrightnessSlider;
 import com.android.systemui.shared.plugins.PluginManager;
-import com.android.unfold.config.UnfoldTransitionConfig;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.BackDropView;
 import com.android.systemui.statusbar.CircleReveal;
@@ -249,6 +244,7 @@
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.wmshell.BubblesManager;
+import com.android.unfold.config.UnfoldTransitionConfig;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
@@ -268,7 +264,8 @@
 
 import dagger.Lazy;
 
-public class StatusBar extends SystemUI implements DemoMode,
+/** */
+public class StatusBar extends SystemUI implements
         ActivityStarter, KeyguardStateController.Callback,
         OnHeadsUpChangedListener, CommandQueue.Callbacks,
         ColorExtractor.OnColorsChangedListener, ConfigurationListener,
@@ -444,6 +441,7 @@
     private final OngoingCallController mOngoingCallController;
     private final SystemStatusAnimationScheduler mAnimationScheduler;
     private final StatusBarLocationPublisher mStatusBarLocationPublisher;
+    private final StatusBarIconController mStatusBarIconController;
 
     // expanded notifications
     // the sliding/resizing panel within the notification window
@@ -810,6 +808,7 @@
             OngoingCallController ongoingCallController,
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
+            StatusBarIconController statusBarIconController,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
             FeatureFlags featureFlags,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -898,6 +897,7 @@
         mOngoingCallController = ongoingCallController;
         mAnimationScheduler = animationScheduler;
         mStatusBarLocationPublisher = locationPublisher;
+        mStatusBarIconController = statusBarIconController;
         mFeatureFlags = featureFlags;
         mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
@@ -968,8 +968,6 @@
         // Connect in to the status bar manager service
         mCommandQueue.addCallback(this);
 
-        // Listen for demo mode changes
-        mDemoModeController.addCallback(this);
 
         RegisterStatusBarResult result = null;
         try {
@@ -997,7 +995,7 @@
             showTransientUnchecked();
         }
         onSystemBarAttributesChanged(mDisplayId, result.mAppearance, result.mAppearanceRegions,
-                result.mNavbarColorManagedByIme, result.mBehavior, result.mRequestedState,
+                result.mNavbarColorManagedByIme, result.mBehavior, result.mRequestedVisibilities,
                 result.mPackageName);
 
         // StatusBarManagerService has a back up of IME token and it's restored here.
@@ -1205,7 +1203,13 @@
                                 mStatusBarLocationPublisher,
                                 mNotificationIconAreaController,
                                 mFeatureFlags,
-                                () -> Optional.of(this)),
+                                () -> Optional.of(this),
+                                mStatusBarIconController,
+                                mKeyguardStateController,
+                                mNetworkController,
+                                mStatusBarStateController,
+                                mCommandQueue
+                        ),
                         CollapsedStatusBarFragment.TAG)
                 .commit();
 
@@ -1591,6 +1595,9 @@
 
         mAuthRippleController = statusBarComponent.getAuthRippleController();
         mAuthRippleController.init();
+
+        // Listen for demo mode changes
+        mDemoModeController.addCallback(statusBarComponent.getStatusBarDemoMode());
     }
 
     protected void startKeyguard() {
@@ -2506,7 +2513,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsState requestedState, String packageName) {
+            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
         if (displayId != mDisplayId) {
             return;
         }
@@ -2520,7 +2527,7 @@
 
         updateBubblesVisibility();
         mStatusBarStateController.setSystemBarAttributes(
-                appearance, behavior, requestedState, packageName);
+                appearance, behavior, requestedVisibilities, packageName);
     }
 
     @Override
@@ -2753,6 +2760,11 @@
             mScrimController.dump(fd, pw, args);
         }
 
+        if (mLightRevealScrim != null) {
+            pw.println(
+                    "mLightRevealScrim.getRevealAmount(): " + mLightRevealScrim.getRevealAmount());
+        }
+
         if (mStatusBarKeyguardViewManager != null) {
             mStatusBarKeyguardViewManager.dump(pw);
         }
@@ -3361,82 +3373,6 @@
                 delay);
     }
 
-    @Override
-    public List<String> demoCommands() {
-        List<String> s = new ArrayList<>();
-        s.add(DemoMode.COMMAND_BARS);
-        s.add(DemoMode.COMMAND_CLOCK);
-        s.add(DemoMode.COMMAND_OPERATOR);
-        return s;
-    }
-
-    @Override
-    public void onDemoModeStarted() {
-        // Must send this message to any view that we delegate to via dispatchDemoCommandToView
-        dispatchDemoModeStartedToView(R.id.clock);
-        dispatchDemoModeStartedToView(R.id.operator_name);
-    }
-
-    @Override
-    public void onDemoModeFinished() {
-        dispatchDemoModeFinishedToView(R.id.clock);
-        dispatchDemoModeFinishedToView(R.id.operator_name);
-        checkBarModes();
-    }
-
-    @Override
-    public void dispatchDemoCommand(String command, @NonNull Bundle args) {
-        if (command.equals(COMMAND_CLOCK)) {
-            dispatchDemoCommandToView(command, args, R.id.clock);
-        }
-        if (command.equals(COMMAND_BARS)) {
-            String mode = args.getString("mode");
-            int barMode = "opaque".equals(mode) ? MODE_OPAQUE :
-                    "translucent".equals(mode) ? MODE_TRANSLUCENT :
-                    "semi-transparent".equals(mode) ? MODE_SEMI_TRANSPARENT :
-                    "transparent".equals(mode) ? MODE_TRANSPARENT :
-                    "warning".equals(mode) ? MODE_WARNING :
-                    -1;
-            if (barMode != -1) {
-                boolean animate = true;
-                if (mNotificationShadeWindowController != null
-                        && mNotificationShadeWindowViewController.getBarTransitions() != null) {
-                    mNotificationShadeWindowViewController.getBarTransitions().transitionTo(
-                            barMode, animate);
-                }
-                mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
-            }
-        }
-        if (command.equals(COMMAND_OPERATOR)) {
-            dispatchDemoCommandToView(command, args, R.id.operator_name);
-        }
-    }
-
-    //TODO: these should have controllers, and this method should be removed
-    private void dispatchDemoCommandToView(String command, Bundle args, int id) {
-        if (mStatusBarView == null) return;
-        View v = mStatusBarView.findViewById(id);
-        if (v instanceof DemoModeCommandReceiver) {
-            ((DemoModeCommandReceiver) v).dispatchDemoCommand(command, args);
-        }
-    }
-
-    private void dispatchDemoModeStartedToView(int id) {
-        if (mStatusBarView == null) return;
-        View v = mStatusBarView.findViewById(id);
-        if (v instanceof DemoModeCommandReceiver) {
-            ((DemoModeCommandReceiver) v).onDemoModeStarted();
-        }
-    }
-
-    private void dispatchDemoModeFinishedToView(int id) {
-        if (mStatusBarView == null) return;
-        View v = mStatusBarView.findViewById(id);
-        if (v instanceof DemoModeCommandReceiver) {
-            ((DemoModeCommandReceiver) v).onDemoModeFinished();
-        }
-    }
-
     public void showKeyguard() {
         mStatusBarStateController.setKeyguardRequested(true);
         mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
@@ -3950,7 +3886,11 @@
                 || !wakingUp && mWakefulnessLifecycle.getLastSleepReason()
                 == PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON) {
             mLightRevealScrim.setRevealEffect(mPowerButtonReveal);
-        } else if (!(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
+        } else if (!wakingUp || !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
+            // If we're going to sleep, but it's not from the power button, use the default reveal.
+            // If we're waking up, only use the default reveal if the biometric controller didn't
+            // already set it to the circular reveal because we're waking up from a fingerprint/face
+            // auth.
             mLightRevealScrim.setRevealEffect(LiftReveal.INSTANCE);
         }
     }
@@ -3979,7 +3919,7 @@
 
     public void onUnlockHintStarted() {
         mFalsingCollector.onUnlockHintStarted();
-        mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock);
+        mKeyguardIndicationController.showActionToUnlock();
     }
 
     public void onHintFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index edcf261..fe1f63a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.res.Resources
 import android.graphics.Rect
+import android.util.Log
 import android.util.Pair
 import android.view.DisplayCutout
 import android.view.View.LAYOUT_DIRECTION_RTL
@@ -155,13 +156,30 @@
         val dc = context.display.cutout
         val currentRotation = RotationUtils.getExactRotation(context)
 
+        val isRtl = rotatedResources.configuration.layoutDirection == LAYOUT_DIRECTION_RTL
+        val roundedCornerPadding = rotatedResources
+                .getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
+        val minDotWidth = rotatedResources
+                .getDimensionPixelSize(R.dimen.ongoing_appops_dot_min_padding)
+
+        val minLeft: Int
+        val minRight: Int
+        if (isRtl) {
+            minLeft = max(minDotWidth, roundedCornerPadding)
+            minRight = roundedCornerPadding
+        } else {
+            minLeft = roundedCornerPadding
+            minRight = max(minDotWidth, roundedCornerPadding)
+        }
+
         return calculateInsetsForRotationWithRotatedResources(
                 currentRotation,
                 targetRotation,
                 dc,
                 windowManager.maximumWindowMetrics,
                 rotatedResources.getDimensionPixelSize(R.dimen.status_bar_height),
-                rotatedResources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding))
+                minLeft,
+                minRight)
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
@@ -212,9 +230,12 @@
  * Calculates the exact left and right positions for the status bar contents for the given
  * rotation
  *
- * @param rot rotation for which to query the margins
- * @param context systemui context
- * @param rotatedResources resources constructed with the proper orientation set
+ * @param currentRotation current device rotation
+ * @param targetRotation rotation for which to calculate the status bar content rect
+ * @param displayCutout [DisplayCutout] for the curren display. possibly null
+ * @param windowMetrics [WindowMetrics] for the current window
+ * @param statusBarHeight height of the status bar for the target rotation
+ * @param roundedCornerPadding from rounded_corner_content_padding
  *
  * @see [RotationUtils#getResourcesForRotation]
  */
@@ -224,7 +245,8 @@
     displayCutout: DisplayCutout?,
     windowMetrics: WindowMetrics,
     statusBarHeight: Int,
-    roundedCornerPadding: Int
+    minLeft: Int,
+    minRight: Int
 ): Rect {
     /*
     TODO: Check if this is ever used for devices with no rounded corners
@@ -242,7 +264,8 @@
             rotZeroBounds.bottom,
             currentBounds.width(),
             currentBounds.height(),
-            roundedCornerPadding,
+            minLeft,
+            minRight,
             targetRotation,
             currentRotation)
 
@@ -256,7 +279,10 @@
  * @param sbHeight appropriate status bar height for this rotation
  * @param width display width calculated for ROTATION_NONE
  * @param height display height calculated for ROTATION_NONE
- * @param roundedCornerPadding rounded_corner_content_padding dimension
+ * @param cWidth display width in our current rotation
+ * @param cHeight display height in our current rotation
+ * @param minLeft the minimum padding to enforce on the left
+ * @param minRight the minimum padding to enforce on the right
  * @param targetRotation the rotation for which to calculate margins
  * @param currentRotation the rotation from which the display cutout was generated
  *
@@ -270,7 +296,8 @@
     height: Int,
     cWidth: Int,
     cHeight: Int,
-    roundedCornerPadding: Int,
+    minLeft: Int,
+    minRight: Int,
     @Rotation targetRotation: Int,
     @Rotation currentRotation: Int
 ): Rect {
@@ -279,9 +306,9 @@
 
     val cutoutRects = dc?.boundingRects
     if (cutoutRects == null || cutoutRects.isEmpty()) {
-        return Rect(roundedCornerPadding,
+        return Rect(minLeft,
                 0,
-                logicalDisplayWidth - roundedCornerPadding,
+                logicalDisplayWidth - minRight,
                 sbHeight)
     }
 
@@ -294,8 +321,8 @@
     // Size of the status bar window for the given rotation relative to our exact rotation
     val sbRect = sbRect(relativeRotation, sbHeight, Pair(cWidth, cHeight))
 
-    var leftMargin = roundedCornerPadding
-    var rightMargin = roundedCornerPadding
+    var leftMargin = minLeft
+    var rightMargin = minRight
     for (cutoutRect in cutoutRects) {
         // There is at most one non-functional area per short edge of the device. So if the status
         // bar doesn't share a short edge with the cutout, we can ignore its insets because there
@@ -306,11 +333,11 @@
 
         if (cutoutRect.touchesLeftEdge(relativeRotation, cWidth, cHeight)) {
 
-            val l = max(roundedCornerPadding, cutoutRect.logicalWidth(relativeRotation))
+            val l = max(minLeft, cutoutRect.logicalWidth(relativeRotation))
             leftMargin = max(l, leftMargin)
         } else if (cutoutRect.touchesRightEdge(relativeRotation, cWidth, cHeight)) {
             val logicalWidth = cutoutRect.logicalWidth(relativeRotation)
-            rightMargin = max(roundedCornerPadding, logicalWidth)
+            rightMargin = max(minRight, logicalWidth)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
new file mode 100644
index 0000000..8938f96
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
@@ -0,0 +1,144 @@
+/*
+ * 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.statusbar.phone;
+
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.demomode.DemoMode;
+import com.android.systemui.demomode.DemoModeCommandReceiver;
+import com.android.systemui.navigationbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/** */
+@StatusBarComponent.StatusBarScope
+public class StatusBarDemoMode implements DemoMode {
+    private final StatusBar mStatusBar;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private final NavigationBarController mNavigationBarController;
+    private final int mDisplayId;
+
+    @Inject
+    StatusBarDemoMode(
+            StatusBar statusBar,
+            NotificationShadeWindowController notificationShadeWindowController,
+            NotificationShadeWindowViewController notificationShadeWindowViewController,
+            NavigationBarController navigationBarController,
+            @DisplayId int displayId) {
+        mStatusBar = statusBar;
+        mNotificationShadeWindowController = notificationShadeWindowController;
+        mNotificationShadeWindowViewController = notificationShadeWindowViewController;
+        mNavigationBarController = navigationBarController;
+        mDisplayId = displayId;
+    }
+
+    @Override
+    public List<String> demoCommands() {
+        List<String> s = new ArrayList<>();
+        s.add(DemoMode.COMMAND_BARS);
+        s.add(DemoMode.COMMAND_CLOCK);
+        s.add(DemoMode.COMMAND_OPERATOR);
+        return s;
+    }
+
+    @Override
+    public void onDemoModeStarted() {
+        // Must send this message to any view that we delegate to via dispatchDemoCommandToView
+        dispatchDemoModeStartedToView(R.id.clock);
+        dispatchDemoModeStartedToView(R.id.operator_name);
+    }
+
+    @Override
+    public void onDemoModeFinished() {
+        dispatchDemoModeFinishedToView(R.id.clock);
+        dispatchDemoModeFinishedToView(R.id.operator_name);
+        mStatusBar.checkBarModes();
+    }
+
+    @Override
+    public void dispatchDemoCommand(String command, @NonNull Bundle args) {
+        if (command.equals(COMMAND_CLOCK)) {
+            dispatchDemoCommandToView(command, args, R.id.clock);
+        }
+        if (command.equals(COMMAND_BARS)) {
+            String mode = args.getString("mode");
+            int barMode = "opaque".equals(mode) ? MODE_OPAQUE :
+                    "translucent".equals(mode) ? MODE_TRANSLUCENT :
+                            "semi-transparent".equals(mode) ? MODE_SEMI_TRANSPARENT :
+                                    "transparent".equals(mode) ? MODE_TRANSPARENT :
+                                            "warning".equals(mode) ? MODE_WARNING :
+                                                    -1;
+            if (barMode != -1) {
+                boolean animate = true;
+                if (mNotificationShadeWindowController != null
+                        && mNotificationShadeWindowViewController.getBarTransitions() != null) {
+                    mNotificationShadeWindowViewController.getBarTransitions().transitionTo(
+                            barMode, animate);
+                }
+                mNavigationBarController.transitionTo(mDisplayId, barMode, animate);
+            }
+        }
+        if (command.equals(COMMAND_OPERATOR)) {
+            dispatchDemoCommandToView(command, args, R.id.operator_name);
+        }
+    }
+
+
+    private void dispatchDemoModeStartedToView(int id) {
+        View statusBarView = mStatusBar.getStatusBarView();
+        if (statusBarView == null) return;
+        View v = statusBarView.findViewById(id);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).onDemoModeStarted();
+        }
+    }
+
+    //TODO: these should have controllers, and this method should be removed
+    private void dispatchDemoCommandToView(String command, Bundle args, int id) {
+        View statusBarView = mStatusBar.getStatusBarView();
+        if (statusBarView == null) return;
+        View v = statusBarView.findViewById(id);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).dispatchDemoCommand(command, args);
+        }
+    }
+
+    private void dispatchDemoModeFinishedToView(int id) {
+        View statusBarView = mStatusBar.getStatusBarView();
+        if (statusBarView == null) return;
+        View v = statusBarView.findViewById(id);
+        if (v instanceof DemoModeCommandReceiver) {
+            ((DemoModeCommandReceiver) v).onDemoModeFinished();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 2c75534..dbe4c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -35,6 +35,7 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoModeCommandReceiver;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
@@ -50,6 +51,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 public interface StatusBarIconController {
 
     /**
@@ -213,6 +216,20 @@
             icons.setColor(mColor);
             return icons;
         }
+
+        @SysUISingleton
+        public static class Factory {
+            private final FeatureFlags mFeatureFlags;
+
+            @Inject
+            public Factory(FeatureFlags featureFlags) {
+                mFeatureFlags = featureFlags;
+            }
+
+            public TintedIconManager create(ViewGroup group) {
+                return new TintedIconManager(group, mFeatureFlags);
+            }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 75900a2..9d1c1e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -23,6 +23,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.view.ViewGroup;
 
 import com.android.internal.statusbar.StatusBarIcon;
@@ -88,6 +89,13 @@
     /** */
     @Override
     public void addIconGroup(IconManager group) {
+        for (IconManager existingIconManager : mIconGroups) {
+            if (existingIconManager.mGroup == group.mGroup) {
+                Log.e(TAG, "Adding new IconManager for the same ViewGroup. This could cause "
+                        + "unexpected results.");
+            }
+        }
+
         mIconGroups.add(group);
         List<Slot> allSlots = getSlots();
         for (int i = 0; i < allSlots.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index b982133..cb844d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -24,12 +24,14 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
 import android.util.Log;
 import android.util.Slog;
+import android.view.View;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.TextView;
 
@@ -392,8 +394,10 @@
     }
 
     @Override
-    public void onExpandClicked(NotificationEntry clickedEntry, boolean nowExpanded) {
+    public void onExpandClicked(NotificationEntry clickedEntry, View clickedView,
+            boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
+        mStatusBar.wakeUpIfDozing(SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK");
         if (nowExpanded) {
             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index 4fab226..a4063bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
 import com.android.systemui.statusbar.phone.SplitShadeHeaderController;
+import com.android.systemui.statusbar.phone.StatusBarDemoMode;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
 
 import java.lang.annotation.Documented;
@@ -89,6 +90,12 @@
     AuthRippleController getAuthRippleController();
 
     /**
+     * Creates a StatusBarDemoMode.
+     */
+    @StatusBarScope
+    StatusBarDemoMode getStatusBarDemoMode();
+
+    /**
      * Creates a SplitShadeHeaderController.
      */
     @StatusBarScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 3dc24e2..5e8eecb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -90,6 +90,7 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
@@ -220,6 +221,7 @@
             OngoingCallController ongoingCallController,
             SystemStatusAnimationScheduler animationScheduler,
             StatusBarLocationPublisher locationPublisher,
+            StatusBarIconController statusBarIconController,
             LockscreenShadeTransitionController transitionController,
             FeatureFlags featureFlags,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
@@ -311,6 +313,7 @@
                 ongoingCallController,
                 animationScheduler,
                 locationPublisher,
+                statusBarIconController,
                 transitionController,
                 featureFlags,
                 keyguardUnlockAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index c2bd87c..3a05ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -30,6 +30,9 @@
     /** Alert controller of a change in between light and dark themes. */
     void notifyThemeChanged();
 
+    /** Query the current configuration's layout direction */
+    boolean isLayoutRtl();
+
     interface ConfigurationListener {
         default void onConfigChanged(Configuration newConfig) {}
         default void onDensityOrFontScaleChanged() {}
@@ -38,5 +41,6 @@
         default void onUiModeChanged() {}
         default void onThemeChanged() {}
         default void onLocaleListChanged() {}
+        default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
new file mode 100644
index 0000000..fbfa5e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureController.java
@@ -0,0 +1,58 @@
+/*
+ * 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.statusbar.policy;
+
+import static com.android.systemui.statusbar.policy.DevicePostureController.Callback;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Listener for device posture changes. This can be used to query the current posture, or register
+ * for events when it changes.
+ */
+public interface DevicePostureController extends CallbackController<Callback> {
+    @IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
+            DEVICE_POSTURE_UNKNOWN,
+            DEVICE_POSTURE_CLOSED,
+            DEVICE_POSTURE_HALF_OPENED,
+            DEVICE_POSTURE_OPENED,
+            DEVICE_POSTURE_FLIPPED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DevicePostureInt {}
+
+    // NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we
+    // use the Device State -> Jetpack Posture map in DevicePostureControllerImpl to translate
+    // between the two.
+    int DEVICE_POSTURE_UNKNOWN = 0;
+    int DEVICE_POSTURE_CLOSED = 1;
+    int DEVICE_POSTURE_HALF_OPENED = 2;
+    int DEVICE_POSTURE_OPENED = 3;
+    int DEVICE_POSTURE_FLIPPED = 4;
+
+    /** Return the current device posture. */
+    @DevicePostureInt int getDevicePosture();
+
+    /** Callback to be notified about device posture changes. */
+    interface Callback {
+        /** Called when the posture changes. */
+        void onPostureChanged(@DevicePostureInt int posture);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
new file mode 100644
index 0000000..8471e0a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DevicePostureControllerImpl.java
@@ -0,0 +1,94 @@
+/*
+ * 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.statusbar.policy;
+
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.util.SparseIntArray;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** Implementation of {@link DevicePostureController} using the DeviceStateManager. */
+@SysUISingleton
+public class DevicePostureControllerImpl implements DevicePostureController {
+    private final List<Callback> mListeners = new ArrayList<>();
+    private int mCurrentDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+    private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray();
+
+    @Inject
+    public DevicePostureControllerImpl(
+            Context context, DeviceStateManager deviceStateManager, @Main Executor executor) {
+        // Most of this is borrowed from WindowManager/Jetpack/DeviceStateManagerPostureProducer.
+        // Using the sidecar/extension libraries directly brings in a new dependency that it'd be
+        // good to avoid (along with the fact that sidecar is deprecated, and extensions isn't fully
+        // ready yet), and we'd have to make our own layer over the sidecar library anyway to easily
+        // allow the implementation to change, so it was easier to just interface with
+        // DeviceStateManager directly.
+        String[] deviceStatePosturePairs = context.getResources()
+                .getStringArray(R.array.config_device_state_postures);
+        for (String deviceStatePosturePair : deviceStatePosturePairs) {
+            String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
+            if (deviceStatePostureMapping.length != 2) {
+                continue;
+            }
+
+            int deviceState;
+            int posture;
+            try {
+                deviceState = Integer.parseInt(deviceStatePostureMapping[0]);
+                posture = Integer.parseInt(deviceStatePostureMapping[1]);
+            } catch (NumberFormatException e) {
+                continue;
+            }
+
+            mDeviceStateToPostureMap.put(deviceState, posture);
+        }
+
+        deviceStateManager.registerCallback(executor, state -> {
+            mCurrentDevicePosture =
+                    mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN);
+
+            mListeners.forEach(l -> l.onPostureChanged(mCurrentDevicePosture));
+        });
+    }
+
+    @Override
+    public void addCallback(@NonNull Callback listener) {
+        mListeners.add(listener);
+    }
+
+    @Override
+    public void removeCallback(@NonNull Callback listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public int getDevicePosture() {
+        return mCurrentDevicePosture;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index c94eaed..ae73f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -23,6 +23,7 @@
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.IActivityTaskManager;
 import android.content.BroadcastReceiver;
@@ -71,9 +72,11 @@
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.QSUserSwitcherEvent;
 import com.android.systemui.qs.tiles.UserDetailView;
+import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.CreateUserActivity;
+import com.android.systemui.util.settings.SecureSettings;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -104,9 +107,12 @@
     private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
 
     protected final Context mContext;
+    protected final UserTracker mUserTracker;
     protected final UserManager mUserManager;
+    private final ContentObserver mSettingsObserver;
     private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
-    private final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
+    @VisibleForTesting
+    final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
     private final KeyguardStateController mKeyguardStateController;
     protected final Handler mHandler;
     private final ActivityStarter mActivityStarter;
@@ -115,15 +121,18 @@
     private final IActivityTaskManager mActivityTaskManager;
 
     private ArrayList<UserRecord> mUsers = new ArrayList<>();
-    private Dialog mExitGuestDialog;
-    private Dialog mAddUserDialog;
+    @VisibleForTesting
+    AlertDialog mExitGuestDialog;
+    @VisibleForTesting
+    Dialog mAddUserDialog;
     private int mLastNonGuestUser = UserHandle.USER_SYSTEM;
     private boolean mResumeUserOnGuestLogout = true;
     private boolean mSimpleUserSwitcher;
     // When false, there won't be any visual affordance to add a new user from the keyguard even if
     // the user is unlocked
     private boolean mAddUsersFromLockScreen;
-    private boolean mPauseRefreshUsers;
+    @VisibleForTesting
+    boolean mPauseRefreshUsers;
     private int mSecondaryUser = UserHandle.USER_NULL;
     private Intent mSecondaryUserServiceIntent;
     private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
@@ -131,11 +140,14 @@
     public final DetailAdapter mUserDetailAdapter;
     private final Executor mUiBgExecutor;
     private final boolean mGuestUserAutoCreated;
+    private final AtomicBoolean mGuestIsResetting;
     private final AtomicBoolean mGuestCreationScheduled;
     private FalsingManager mFalsingManager;
 
     @Inject
     public UserSwitcherController(Context context,
+            UserManager userManager,
+            UserTracker userTracker,
             KeyguardStateController keyguardStateController,
             @Main Handler handler,
             ActivityStarter activityStarter,
@@ -145,14 +157,17 @@
             TelephonyListenerManager telephonyListenerManager,
             IActivityTaskManager activityTaskManager,
             UserDetailAdapter userDetailAdapter,
+            SecureSettings secureSettings,
             @UiBackground Executor uiBgExecutor) {
         mContext = context;
+        mUserTracker = userTracker;
         mBroadcastDispatcher = broadcastDispatcher;
         mTelephonyListenerManager = telephonyListenerManager;
         mActivityTaskManager = activityTaskManager;
         mUiEventLogger = uiEventLogger;
         mFalsingManager = falsingManager;
-        mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(this, mUiEventLogger);
+        mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
+                this, mUserTracker, mUiEventLogger, secureSettings);
         mUserDetailAdapter = userDetailAdapter;
         mUiBgExecutor = uiBgExecutor;
         if (!UserManager.isGuestUserEphemeral()) {
@@ -160,11 +175,12 @@
         }
         mGuestUserAutoCreated = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_guestUserAutoCreated);
+        mGuestIsResetting = new AtomicBoolean();
         mGuestCreationScheduled = new AtomicBoolean();
         mKeyguardStateController = keyguardStateController;
         mHandler = handler;
         mActivityStarter = activityStarter;
-        mUserManager = UserManager.get(context);
+        mUserManager = userManager;
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_REMOVED);
@@ -183,6 +199,15 @@
         mContext.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, filter,
                 PERMISSION_SELF, null /* scheduler */);
 
+        mSettingsObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
+                mAddUsersFromLockScreen = Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
+                refreshUsers(UserHandle.USER_NULL);
+            };
+        };
         mContext.getContentResolver().registerContentObserver(
                 Settings.Global.getUriFor(SIMPLE_USER_SWITCHER_GLOBAL_SETTING), true,
                 mSettingsObserver);
@@ -244,11 +269,11 @@
                     return null;
                 }
                 ArrayList<UserRecord> records = new ArrayList<>(infos.size());
-                int currentId = ActivityManager.getCurrentUser();
+                int currentId = mUserTracker.getUserId();
                 // Check user switchability of the foreground user since SystemUI is running in
                 // User 0
                 boolean canSwitchUsers = mUserManager.getUserSwitchability(
-                        UserHandle.of(ActivityManager.getCurrentUser())) == SWITCHABILITY_STATUS_OK;
+                        UserHandle.of(mUserTracker.getUserId())) == SWITCHABILITY_STATUS_OK;
                 UserInfo currentUserInfo = null;
                 UserRecord guestRecord = null;
 
@@ -301,7 +326,20 @@
                 boolean createIsRestricted = !addUsersWhenLocked;
 
                 if (guestRecord == null) {
-                    if (canCreateGuest) {
+                    if (mGuestUserAutoCreated) {
+                        // If mGuestIsResetting=true, the switch should be disabled since
+                        // we will just use it as an indicator for "Resetting guest...".
+                        // Otherwise, default to canSwitchUsers.
+                        boolean isSwitchToGuestEnabled =
+                                !mGuestIsResetting.get() && canSwitchUsers;
+                        guestRecord = new UserRecord(null /* info */, null /* picture */,
+                                true /* isGuest */, false /* isCurrent */,
+                                false /* isAddUser */, false /* isRestricted */,
+                                isSwitchToGuestEnabled);
+                        // Don't call checkIfAddUserDisallowedByAdminOnly if
+                        // config_guestUserAutoCreated=true.
+                        records.add(guestRecord);
+                    } else if (canCreateGuest) {
                         guestRecord = new UserRecord(null /* info */, null /* picture */,
                                 true /* isGuest */, false /* isCurrent */,
                                 false /* isAddUser */, createIsRestricted, canSwitchUsers);
@@ -376,7 +414,7 @@
     }
 
     public void logoutCurrentUser() {
-        int currentUser = ActivityManager.getCurrentUser();
+        int currentUser = mUserTracker.getUserId();
         if (currentUser != UserHandle.USER_SYSTEM) {
             pauseRefreshUsers();
             ActivityManager.logoutCurrentUser();
@@ -388,7 +426,7 @@
             Log.w(TAG, "User " + userId + " could not removed.");
             return;
         }
-        if (ActivityManager.getCurrentUser() == userId) {
+        if (mUserTracker.getUserId() == userId) {
             switchToUserId(UserHandle.USER_SYSTEM);
         }
         if (mUserManager.removeUser(userId)) {
@@ -396,7 +434,8 @@
         }
     }
 
-    private void onUserListItemClicked(UserRecord record) {
+    @VisibleForTesting
+    void onUserListItemClicked(UserRecord record) {
         int id;
         if (record.isGuest && record.info == null) {
             // No guest user. Create one.
@@ -414,7 +453,7 @@
             id = record.info.id;
         }
 
-        int currUserId = ActivityManager.getCurrentUser();
+        int currUserId = mUserTracker.getUserId();
         if (currUserId == id) {
             if (record.isGuest) {
                 showExitGuestDialog(id);
@@ -572,15 +611,6 @@
         }
     };
 
-    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
-        public void onChange(boolean selfChange) {
-            mSimpleUserSwitcher = shouldUseSimpleUserSwitcher();
-            mAddUsersFromLockScreen = Settings.Global.getInt(mContext.getContentResolver(),
-                    Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0;
-            refreshUsers(UserHandle.USER_NULL);
-        };
-    };
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("UserSwitcherController state:");
@@ -639,13 +669,7 @@
      * UserHandle.USER_NULL}, then switch immediately to the newly created guest user.
      */
     public void removeGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId) {
-        UserInfo currentUser;
-        try {
-            currentUser = ActivityManager.getService().getCurrentUser();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Couldn't remove guest because ActivityManager is dead");
-            return;
-        }
+        UserInfo currentUser = mUserTracker.getUserInfo();
         if (currentUser.id != guestUserId) {
             Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
                     + " is not current user (" + currentUser.id + ")");
@@ -677,6 +701,9 @@
                 switchToUserId(newGuestId);
                 mUserManager.removeUser(currentUser.id);
             } else {
+                if (mGuestUserAutoCreated) {
+                    mGuestIsResetting.set(true);
+                }
                 switchToUserId(targetUserId);
                 mUserManager.removeUser(currentUser.id);
             }
@@ -693,10 +720,14 @@
 
         mUiBgExecutor.execute(() -> {
             int newGuestId = createGuest();
+            mGuestCreationScheduled.set(false);
+            mGuestIsResetting.set(false);
             if (newGuestId == UserHandle.USER_NULL) {
                 Log.w(TAG, "Could not create new guest while exiting existing guest");
+                // Refresh users so that we still display "Guest" if
+                // config_guestUserAutoCreated=true
+                refreshUsers(UserHandle.USER_NULL);
             }
-            mGuestCreationScheduled.set(false);
         });
 
     }
@@ -799,12 +830,25 @@
                             ? com.android.settingslib.R.string.guest_reset_guest
                             : com.android.settingslib.R.string.guest_exit_guest);
                 } else {
-                    // If config_guestUserAutoCreated, always show guest nickname instead of "Add
-                    // guest" to make it seem as though the device always has a guest ready for use
-                    return context.getString(
-                            item.info == null && !mController.mGuestUserAutoCreated
-                                    ? com.android.settingslib.R.string.guest_new_guest
-                                    : com.android.settingslib.R.string.guest_nickname);
+                    if (item.info != null) {
+                        return context.getString(com.android.settingslib.R.string.guest_nickname);
+                    } else {
+                        if (mController.mGuestUserAutoCreated) {
+                            // If mGuestIsResetting=true, we expect the guest user to be created
+                            // shortly, so display a "Resetting guest..." as an indicator that we
+                            // are busy. Otherwise, if mGuestIsResetting=false, we probably failed
+                            // to create a guest at some point. In this case, always show guest
+                            // nickname instead of "Add guest" to make it seem as though the device
+                            // always has a guest ready for use.
+                            return context.getString(
+                                    mController.mGuestIsResetting.get()
+                                            ? com.android.settingslib.R.string.guest_resetting
+                                            : com.android.settingslib.R.string.guest_nickname);
+                        } else {
+                            return context.getString(
+                                    com.android.settingslib.R.string.guest_new_guest);
+                        }
+                    }
                 }
             } else if (item.isAddUser) {
                 return context.getString(R.string.user_add_user);
@@ -839,9 +883,9 @@
 
     private void checkIfAddUserDisallowedByAdminOnly(UserRecord record) {
         EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
-                UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser());
+                UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId());
         if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
-                UserManager.DISALLOW_ADD_USER, ActivityManager.getCurrentUser())) {
+                UserManager.DISALLOW_ADD_USER, mUserTracker.getUserId())) {
             record.isDisabledByAdmin = true;
             record.enforcedAdmin = admin;
         } else {
@@ -1053,7 +1097,8 @@
         }
     }
 
-    private final class AddUserDialog extends SystemUIDialog implements
+    @VisibleForTesting
+    final class AddUserDialog extends SystemUIDialog implements
             DialogInterface.OnClickListener {
 
         public AddUserDialog(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 9fb0453..c224cf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -28,6 +28,8 @@
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.DeviceControlsController;
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl;
+import com.android.systemui.statusbar.policy.DevicePostureController;
+import com.android.systemui.statusbar.policy.DevicePostureControllerImpl;
 import com.android.systemui.statusbar.policy.ExtensionController;
 import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
 import com.android.systemui.statusbar.policy.FlashlightController;
@@ -130,6 +132,11 @@
             AccessPointControllerImpl accessPointControllerImpl);
 
     /** */
+    @Binds
+    DevicePostureController provideDevicePostureController(
+            DevicePostureControllerImpl devicePostureControllerImpl);
+
+    /** */
     @SysUISingleton
     @Provides
     static AccessPointControllerImpl  provideAccessPointControllerImpl(
diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
index 0dd5788..32bbe1c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
@@ -110,6 +110,16 @@
     }
 
     /**
+     * Destroys this controller so that it never receives view attach and detach events again.
+     * Does nothing if the view is null.
+     */
+    public void destroy() {
+        if (mView != null) {
+            mView.removeOnAttachStateChangeListener(mOnAttachStateListener);
+        }
+    }
+
+    /**
      * Called when the view is attached and a call to {@link #init()} has been made in either order.
      */
     protected abstract void onViewAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index f2db4f1..db965db 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -242,6 +242,11 @@
     void initSplitScreen(SplitScreen splitScreen) {
         mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
             @Override
+            public void onKeyguardVisibilityChanged(boolean showing) {
+                splitScreen.onKeyguardVisibilityChanged(showing);
+            }
+
+            @Override
             public void onKeyguardOccludedChanged(boolean occluded) {
                 splitScreen.onKeyguardOccludedChanged(occluded);
             }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index e9061af..ec4dfba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -513,7 +513,7 @@
     public void testTriesToAuthenticate_whenBouncer() {
         setKeyguardBouncerVisibility(true);
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
         verify(mFaceManager).isHardwareDetected();
         verify(mFaceManager).hasEnrolledTemplates(anyInt());
     }
@@ -523,7 +523,7 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -533,7 +533,8 @@
         mTestableLooper.processAllMessages();
 
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
     }
 
     @Test
@@ -545,7 +546,8 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
     }
 
     @Test
@@ -568,13 +570,14 @@
         mKeyguardUpdateMonitor.dispatchStartedWakingUp();
         mTestableLooper.processAllMessages();
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
 
         // Stop scanning when bouncer becomes visible
         setKeyguardBouncerVisibility(true);
         clearInvocations(mFaceManager);
         mKeyguardUpdateMonitor.requestFaceAuth(true);
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
     }
 
     @Test
@@ -582,7 +585,7 @@
         mKeyguardUpdateMonitor.setKeyguardOccluded(true);
         mKeyguardUpdateMonitor.setAssistantVisible(true);
 
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -594,7 +597,7 @@
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
                 KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */);
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -604,7 +607,8 @@
         mKeyguardUpdateMonitor.onTrustChanged(true /* enabled */,
                 KeyguardUpdateMonitor.getCurrentUser(), 0 /* flags */);
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
     }
 
     @Test
@@ -615,7 +619,8 @@
                 KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
 
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
     }
 
     @Test
@@ -626,7 +631,7 @@
                 KeyguardUpdateMonitor.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT);
 
         mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
-        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt());
+        verify(mFaceManager).authenticate(any(), any(), any(), any(), anyInt(), anyBoolean());
     }
 
     @Test
@@ -638,7 +643,8 @@
         mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(true);
         mTestableLooper.processAllMessages();
 
-        verify(mFaceManager, never()).authenticate(any(), any(), any(), any());
+        verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
+                anyBoolean());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index d5a2919..be7917a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,7 +33,7 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.fingerprint.IUdfpsHbmListener;
 import android.os.Bundle;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -125,25 +125,25 @@
     public void testOnSystemBarAttributesChanged() {
         doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsState(), "test");
+                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test");
     }
 
     @Test
     public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
         doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsState(), "test");
+                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test");
     }
 
     private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsState requestedState, String packageName) {
+            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
         mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                navbarColorManagedByIme, behavior, requestedState, packageName);
+                navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
         waitForIdleSync();
         verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
                 eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
-                eq(requestedState), eq(packageName));
+                eq(requestedVisibilities), eq(packageName));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 298bd9a..f5ce673 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -81,6 +81,7 @@
 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -146,6 +147,8 @@
     private LockPatternUtils mLockPatternUtils;
     @Mock
     private IActivityManager mIActivityManager;
+    @Mock
+    private KeyguardBypassController mKeyguardBypassController;
     @Captor
     private ArgumentCaptor<DockManager.AlignmentStateListener> mAlignmentListener;
     @Captor
@@ -216,7 +219,8 @@
         mController = new KeyguardIndicationController(mContext, mWakeLockBuilder,
                 mKeyguardStateController, mStatusBarStateController, mKeyguardUpdateMonitor,
                 mDockManager, mBroadcastDispatcher, mDevicePolicyManager, mIBatteryStats,
-                mUserManager, mExecutor, mFalsingManager, mLockPatternUtils, mIActivityManager);
+                mUserManager, mExecutor, mFalsingManager, mLockPatternUtils, mIActivityManager,
+                mKeyguardBypassController);
         mController.init();
         mController.setIndicationArea(mIndicationArea);
         verify(mStatusBarStateController).addCallback(mStatusBarStateListenerCaptor.capture());
@@ -507,6 +511,7 @@
         createController();
         String message = mContext.getString(R.string.keyguard_retry);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
 
         mController.setVisible(true);
         mController.getKeyguardCallback().onBiometricError(FaceManager.FACE_ERROR_TIMEOUT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index 32b08be..b0d1fc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -39,10 +39,14 @@
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
@@ -54,13 +58,19 @@
 @SmallTest
 public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
 
-    private StatusBar mStatusBar;
     private NotificationIconAreaController mMockNotificationAreaController;
     private View mNotificationAreaInner;
-    private StatusBarStateController mStatusBarStateController;
     private OngoingCallController mOngoingCallController;
     private SystemStatusAnimationScheduler mAnimationScheduler;
     private StatusBarLocationPublisher mLocationPublisher;
+    // Set in instantiate()
+    private StatusBarIconController mStatusBarIconController;
+    private NetworkController mNetworkController;
+    private StatusBarStateController mStatusBarStateController;
+    private KeyguardStateController mKeyguardStateController;
+
+    private final StatusBar mStatusBar = mock(StatusBar.class);
+    private final CommandQueue mCommandQueue = mock(CommandQueue.class);
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -68,7 +78,6 @@
 
     @Before
     public void setup() {
-        mStatusBar = mock(StatusBar.class);
         mStatusBarStateController = mDependency
                 .injectMockDependency(StatusBarStateController.class);
         injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
@@ -204,6 +213,7 @@
                 mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
     }
 
+    @Ignore("b/192618546")
     @Test
     public void testOnDozingChanged() throws Exception {
         mFragments.dispatchResume();
@@ -227,6 +237,10 @@
         mOngoingCallController = mock(OngoingCallController.class);
         mAnimationScheduler = mock(SystemStatusAnimationScheduler.class);
         mLocationPublisher = mock(StatusBarLocationPublisher.class);
+        mStatusBarIconController = mock(StatusBarIconController.class);
+        mNetworkController = mock(NetworkController.class);
+        mStatusBarStateController = mock(StatusBarStateController.class);
+        mKeyguardStateController = mock(KeyguardStateController.class);
         setUpNotificationIconAreaController();
         return new CollapsedStatusBarFragment(
                 mOngoingCallController,
@@ -234,7 +248,12 @@
                 mLocationPublisher,
                 mMockNotificationAreaController,
                 mock(FeatureFlags.class),
-                () -> Optional.of(mStatusBar));
+                () -> Optional.of(mStatusBar),
+                mStatusBarIconController,
+                mKeyguardStateController,
+                mNetworkController,
+                mStatusBarStateController,
+                mCommandQueue);
     }
 
     private void setUpNotificationIconAreaController() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
new file mode 100644
index 0000000..217a77d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.statusbar.phone;
+
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.view.ViewGroup;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.CarrierTextController;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
+    @Mock
+    private KeyguardStatusBarView mKeyguardStatusBarView;
+    @Mock
+    private ViewGroup mViewGroup;
+    @Mock
+    private CarrierTextController mCarrierTextController;
+    @Mock
+    private ConfigurationController mConfigurationController;
+    @Mock
+    private SystemStatusAnimationScheduler mAnimationScheduler;
+    @Mock
+    private BatteryController mBatteryController;
+    @Mock
+    private UserInfoController mUserInfoController;
+    @Mock
+    private StatusBarIconController mStatusBarIconController;
+    @Mock
+    private FeatureFlags mFeatureFlags;
+
+    private KeyguardStatusBarViewController mController;
+
+    @Before
+    public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mKeyguardStatusBarView.getResources()).thenReturn(mContext.getResources());
+        when(mKeyguardStatusBarView.findViewById(R.id.statusIcons)).thenReturn(mViewGroup);
+        when(mViewGroup.getContext()).thenReturn(mContext);
+
+        mController = new KeyguardStatusBarViewController(
+                mKeyguardStatusBarView,
+                mCarrierTextController,
+                mConfigurationController,
+                mAnimationScheduler,
+                mBatteryController,
+                mUserInfoController,
+                mStatusBarIconController,
+                new StatusBarIconController.TintedIconManager.Factory(mFeatureFlags)
+        );
+    }
+
+    @Test
+    public void onViewAttached_callbacksRegistered() {
+        mController.onViewAttached();
+
+        verify(mConfigurationController).addCallback(any());
+        verify(mAnimationScheduler).addCallback(any());
+        verify(mUserInfoController).addCallback(any());
+        verify(mStatusBarIconController).addIconGroup(any());
+    }
+
+    @Test
+    public void onViewDetached_callbacksUnregistered() {
+        // Set everything up first.
+        mController.onViewAttached();
+
+        mController.onViewDetached();
+
+        verify(mConfigurationController).removeCallback(any());
+        verify(mAnimationScheduler).removeCallback(any());
+        verify(mUserInfoController).removeCallback(any());
+        verify(mStatusBarIconController).removeIconGroup(any());
+    }
+
+    @Test
+    public void setBatteryListening_true_callbackAdded() {
+        mController.setBatteryListening(true);
+
+        verify(mBatteryController).addCallback(any());
+    }
+
+    @Test
+    public void setBatteryListening_false_callbackRemoved() {
+        // First set to true so that we know setting to false is a change in state.
+        mController.setBatteryListening(true);
+
+        mController.setBatteryListening(false);
+
+        verify(mBatteryController).removeCallback(any());
+    }
+
+    @Test
+    public void setBatteryListening_trueThenTrue_callbackAddedOnce() {
+        mController.setBatteryListening(true);
+        mController.setBatteryListening(true);
+
+        verify(mBatteryController).addCallback(any());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index c3adee9..74f08ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -102,7 +102,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedState */,
+                null /* requestedVisibilities */,
                 null /* packageName */);
         assertTrue(mLightsOutNotifController.areLightsOut());
     }
@@ -115,7 +115,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedState */,
+                null /* requestedVisibilities */,
                 null /* packageName */);
         assertFalse(mLightsOutNotifController.areLightsOut());
     }
@@ -146,7 +146,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedState */,
+                null /* requestedVisibilities */,
                 null /* packageName */);
 
         // THEN we should show dot
@@ -166,7 +166,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedState */,
+                null /* requestedVisibilities */,
                 null /* packageName */);
 
         // THEN we shouldn't show the dot
@@ -186,7 +186,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedState */,
+                null /* requestedVisibilities */,
                 null /* packageName */);
 
         // THEN we shouldn't show the dot
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index d2f87ec..cbaca3a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -88,6 +88,7 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.controls.dagger.ControlsComponent;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.fragments.FragmentService;
@@ -297,6 +298,8 @@
     private NotificationRemoteInputManager mNotificationRemoteInputManager;
     @Mock
     private RecordingController mRecordingController;
+    @Mock
+    private ControlsComponent mControlsComponent;
 
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -440,7 +443,8 @@
                 mSecureSettings,
                 mSplitShadeHeaderController,
                 mUnlockedScreenOffAnimationController,
-                mNotificationRemoteInputManager);
+                mNotificationRemoteInputManager,
+                mControlsComponent);
         mNotificationPanelViewController.initDependencies(
                 mStatusBar,
                 mNotificationShelfController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 4796cd7..10eb71f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -47,7 +47,8 @@
     @Test
     fun testGetBoundingRectForPrivacyChipForRotation_noCutout() {
         val screenBounds = Rect(0, 0, 1080, 2160)
-        val roundedCornerPadding = 20
+        val minLeftPadding = 20
+        val minRightPadding = 20
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
         val currentRotation = ROTATION_NONE
@@ -64,7 +65,8 @@
                 null,
                 windowMetrics,
                 sbHeightPortrait,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
         /* 1080 - 20 (rounded corner) - 30 (chip),
@@ -92,7 +94,8 @@
                 dc,
                 windowMetrics,
                 sbHeightLandscape,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl)
         /* 2160 - 20 (rounded corner) - 30 (chip),
@@ -118,7 +121,8 @@
         // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
         val screenBounds = Rect(0, 0, 1080, 2160)
         val dcBounds = Rect(0, 0, 100, 100)
-        val roundedCornerPadding = 20
+        val minLeftPadding = 20
+        val minRightPadding = 20
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
         val currentRotation = ROTATION_NONE
@@ -131,7 +135,7 @@
         var targetRotation = ROTATION_NONE
         var expectedBounds = Rect(dcBounds.right,
                 0,
-                screenBounds.right - roundedCornerPadding,
+                screenBounds.right - minRightPadding,
                 sbHeightPortrait)
 
         var bounds = calculateInsetsForRotationWithRotatedResources(
@@ -140,14 +144,15 @@
                 dc,
                 windowMetrics,
                 sbHeightPortrait,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
         expectedBounds = Rect(dcBounds.height(),
                 0,
-                screenBounds.height() - roundedCornerPadding,
+                screenBounds.height() - minRightPadding,
                 sbHeightLandscape)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -156,16 +161,17 @@
                 dc,
                 windowMetrics,
                 sbHeightLandscape,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         // THEN the side that does NOT share a short side with the display cutout ignores the
         // display cutout bounds
         targetRotation = ROTATION_UPSIDE_DOWN
-        expectedBounds = Rect(roundedCornerPadding,
+        expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.width() - roundedCornerPadding,
+                screenBounds.width() - minRightPadding,
                 sbHeightPortrait)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -174,13 +180,14 @@
                 dc,
                 windowMetrics,
                 sbHeightPortrait,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         // Phone in portrait, seascape (rot_270) bounds
         targetRotation = ROTATION_SEASCAPE
-        expectedBounds = Rect(roundedCornerPadding,
+        expectedBounds = Rect(minLeftPadding,
                 0,
                 screenBounds.height() - dcBounds.height(),
                 sbHeightLandscape)
@@ -191,7 +198,8 @@
                 dc,
                 windowMetrics,
                 sbHeightLandscape,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
@@ -205,7 +213,8 @@
         val screenBounds = Rect(0, 0, 1080, 2160)
         // cutout centered at the top
         val dcBounds = Rect(490, 0, 590, 100)
-        val roundedCornerPadding = 20
+        val minLeftPadding = 20
+        val minRightPadding = 20
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
         val currentRotation = ROTATION_NONE
@@ -216,9 +225,9 @@
         // THEN only the landscape/seascape rotations should avoid the cutout area because of the
         // potential letterboxing
         var targetRotation = ROTATION_NONE
-        var expectedBounds = Rect(roundedCornerPadding,
+        var expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.right - roundedCornerPadding,
+                screenBounds.right - minRightPadding,
                 sbHeightPortrait)
 
         var bounds = calculateInsetsForRotationWithRotatedResources(
@@ -227,14 +236,15 @@
                 dc,
                 windowMetrics,
                 sbHeightPortrait,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
         expectedBounds = Rect(dcBounds.height(),
                 0,
-                screenBounds.height() - roundedCornerPadding,
+                screenBounds.height() - minRightPadding,
                 sbHeightLandscape)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -243,14 +253,15 @@
                 dc,
                 windowMetrics,
                 sbHeightLandscape,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_UPSIDE_DOWN
-        expectedBounds = Rect(roundedCornerPadding,
+        expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.right - roundedCornerPadding,
+                screenBounds.right - minRightPadding,
                 sbHeightPortrait)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -259,12 +270,13 @@
                 dc,
                 windowMetrics,
                 sbHeightPortrait,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_SEASCAPE
-        expectedBounds = Rect(roundedCornerPadding,
+        expectedBounds = Rect(minLeftPadding,
                 0,
                 screenBounds.height() - dcBounds.height(),
                 sbHeightLandscape)
@@ -275,7 +287,8 @@
                 dc,
                 windowMetrics,
                 sbHeightLandscape,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
 
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
@@ -285,7 +298,8 @@
         // GIVEN device in portrait mode, where width < height and no cutout
         val currentRotation = ROTATION_NONE
         val screenBounds = Rect(0, 0, 1080, 2160)
-        val roundedCornerPadding = 20
+        val minLeftPadding = 20
+        val minRightPadding = 20
         val sbHeightPortrait = 100
         val sbHeightLandscape = 60
 
@@ -293,9 +307,9 @@
 
         // THEN content insets should only use rounded corner padding
         var targetRotation = ROTATION_NONE
-        var expectedBounds = Rect(roundedCornerPadding,
+        var expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.right - roundedCornerPadding,
+                screenBounds.right - minRightPadding,
                 sbHeightPortrait)
 
         var bounds = calculateInsetsForRotationWithRotatedResources(
@@ -304,13 +318,14 @@
                 null, /* no cutout */
                 windowMetrics,
                 sbHeightPortrait,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
-        expectedBounds = Rect(roundedCornerPadding,
+        expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.height() - roundedCornerPadding,
+                screenBounds.height() - minRightPadding,
                 sbHeightLandscape)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -319,13 +334,14 @@
                 null, /* no cutout */
                 windowMetrics,
                 sbHeightLandscape,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_UPSIDE_DOWN
-        expectedBounds = Rect(roundedCornerPadding,
+        expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.width() - roundedCornerPadding,
+                screenBounds.width() - minRightPadding,
                 sbHeightPortrait)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -334,13 +350,14 @@
                 null, /* no cutout */
                 windowMetrics,
                 sbHeightPortrait,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
 
         targetRotation = ROTATION_LANDSCAPE
-        expectedBounds = Rect(roundedCornerPadding,
+        expectedBounds = Rect(minLeftPadding,
                 0,
-                screenBounds.height() - roundedCornerPadding,
+                screenBounds.height() - minRightPadding,
                 sbHeightLandscape)
 
         bounds = calculateInsetsForRotationWithRotatedResources(
@@ -349,7 +366,41 @@
                 null, /* no cutout */
                 windowMetrics,
                 sbHeightLandscape,
-                roundedCornerPadding)
+                minLeftPadding,
+                minRightPadding)
+        assertRects(expectedBounds, bounds, currentRotation, targetRotation)
+    }
+
+    @Test
+    fun testMinLeftRight_accountsForDisplayCutout() {
+        // GIVEN a device in portrait mode with width < height and a display cutout in the top-left
+        val screenBounds = Rect(0, 0, 1080, 2160)
+        val dcBounds = Rect(0, 0, 100, 100)
+        val minLeftPadding = 80
+        val minRightPadding = 150
+        val sbHeightPortrait = 100
+        val sbHeightLandscape = 60
+        val currentRotation = ROTATION_NONE
+
+        `when`(windowMetrics.bounds).thenReturn(screenBounds)
+        `when`(dc.boundingRects).thenReturn(listOf(dcBounds))
+
+        // THEN left should be set to the display cutout width, and right should use the minRight
+        var targetRotation = ROTATION_NONE
+        var expectedBounds = Rect(dcBounds.right,
+                0,
+                screenBounds.right - minRightPadding,
+                sbHeightPortrait)
+
+        var bounds = calculateInsetsForRotationWithRotatedResources(
+                currentRotation,
+                targetRotation,
+                dc,
+                windowMetrics,
+                sbHeightPortrait,
+                minLeftPadding,
+                minRightPadding)
+
         assertRects(expectedBounds, bounds, currentRotation, targetRotation)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 9b46568..2a58f7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -271,6 +271,7 @@
     @Mock private OngoingCallController mOngoingCallController;
     @Mock private SystemStatusAnimationScheduler mAnimationScheduler;
     @Mock private StatusBarLocationPublisher mLocationPublisher;
+    @Mock private StatusBarIconController mIconController;
     @Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private IWallpaperManager mWallpaperManager;
@@ -444,6 +445,7 @@
                 mOngoingCallController,
                 mAnimationScheduler,
                 mLocationPublisher,
+                mIconController,
                 mLockscreenTransitionController,
                 mFeatureFlags,
                 mKeyguardUnlockAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
new file mode 100644
index 0000000..ace2c71
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -0,0 +1,257 @@
+/*
+ * 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.statusbar.policy
+
+import android.app.IActivityTaskManager
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.hardware.face.FaceManager
+import android.hardware.fingerprint.FingerprintManager
+import android.os.Handler
+import android.os.UserHandle
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.util.UserIcons
+import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.qs.QSUserSwitcherEvent
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class UserSwitcherControllerTest : SysuiTestCase() {
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var handler: Handler
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var userManager: UserManager
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var activityTaskManager: IActivityTaskManager
+    @Mock private lateinit var userDetailAdapter: UserSwitcherController.UserDetailAdapter
+    @Mock private lateinit var telephonyListenerManager: TelephonyListenerManager
+    @Mock private lateinit var secureSettings: SecureSettings
+    @Mock private lateinit var falsingManager: FalsingManager
+    private lateinit var testableLooper: TestableLooper
+    private lateinit var uiBgExecutor: FakeExecutor
+    private lateinit var uiEventLogger: UiEventLoggerFake
+    private lateinit var userSwitcherController: UserSwitcherController
+    private lateinit var picture: Bitmap
+    private val ownerId = UserHandle.USER_SYSTEM
+    private val ownerInfo = UserInfo(ownerId, "Owner", null,
+            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL or UserInfo.FLAG_INITIALIZED or
+                    UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM,
+            UserManager.USER_TYPE_FULL_SYSTEM)
+    private val guestId = 1234
+    private val guestInfo = UserInfo(guestId, "Guest", null,
+            UserInfo.FLAG_FULL or UserInfo.FLAG_GUEST, UserManager.USER_TYPE_FULL_GUEST)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+        uiBgExecutor = FakeExecutor(FakeSystemClock())
+        uiEventLogger = UiEventLoggerFake()
+
+        context.orCreateTestableResources.addOverride(
+                com.android.internal.R.bool.config_guestUserAutoCreated, false)
+
+        context.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
+        context.addMockSystemService(Context.FINGERPRINT_SERVICE,
+                mock(FingerprintManager::class.java))
+
+        `when`(userManager.canAddMoreUsers()).thenReturn(true)
+
+        userSwitcherController = UserSwitcherController(context,
+                userManager,
+                userTracker,
+                keyguardStateController,
+                handler,
+                activityStarter,
+                broadcastDispatcher,
+                uiEventLogger,
+                falsingManager,
+                telephonyListenerManager,
+                activityTaskManager,
+                userDetailAdapter,
+                secureSettings,
+                uiBgExecutor)
+        userSwitcherController.mPauseRefreshUsers = true
+
+        picture = UserIcons.convertToBitmap(context.getDrawable(R.drawable.ic_avatar_user))
+    }
+
+    @Test
+    fun testAddGuest_okButtonPressed_isLogged() {
+        val emptyGuestUserRecord = UserSwitcherController.UserRecord(
+                null,
+                null,
+                true /* guest */,
+                false /* current */,
+                false /* isAddUser */,
+                false /* isRestricted */,
+                true /* isSwitchToEnabled */)
+        `when`(userTracker.userId).thenReturn(ownerId)
+        `when`(userTracker.userInfo).thenReturn(ownerInfo)
+
+        `when`(userManager.createGuest(any(), anyString())).thenReturn(guestInfo)
+
+        userSwitcherController.onUserListItemClicked(emptyGuestUserRecord)
+        testableLooper.processAllMessages()
+        assertEquals(1, uiEventLogger.numLogs())
+        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_ADD.id, uiEventLogger.eventId(0))
+    }
+
+    @Test
+    fun testRemoveGuest_removeButtonPressed_isLogged() {
+        val currentGuestUserRecord = UserSwitcherController.UserRecord(
+                guestInfo,
+                picture,
+                true /* guest */,
+                true /* current */,
+                false /* isAddUser */,
+                false /* isRestricted */,
+                true /* isSwitchToEnabled */)
+        `when`(userTracker.userId).thenReturn(guestInfo.id)
+        `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+        userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+        assertNotNull(userSwitcherController.mExitGuestDialog)
+        userSwitcherController.mExitGuestDialog
+                .getButton(DialogInterface.BUTTON_POSITIVE).performClick()
+        testableLooper.processAllMessages()
+        assertEquals(1, uiEventLogger.numLogs())
+        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id, uiEventLogger.eventId(0))
+    }
+
+    @Test
+    fun testRemoveGuest_cancelButtonPressed_isNotLogged() {
+        val currentGuestUserRecord = UserSwitcherController.UserRecord(
+                guestInfo,
+                picture,
+                true /* guest */,
+                true /* current */,
+                false /* isAddUser */,
+                false /* isRestricted */,
+                true /* isSwitchToEnabled */)
+        `when`(userTracker.userId).thenReturn(guestId)
+        `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+        userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+        assertNotNull(userSwitcherController.mExitGuestDialog)
+        userSwitcherController.mExitGuestDialog
+                .getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
+        testableLooper.processAllMessages()
+        assertEquals(0, uiEventLogger.numLogs())
+    }
+
+    @Test
+    fun testWipeGuest_startOverButtonPressed_isLogged() {
+        val currentGuestUserRecord = UserSwitcherController.UserRecord(
+                guestInfo,
+                picture,
+                true /* guest */,
+                false /* current */,
+                false /* isAddUser */,
+                false /* isRestricted */,
+                true /* isSwitchToEnabled */)
+        `when`(userTracker.userId).thenReturn(guestId)
+        `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+        // Simulate that guest user has already logged in
+        `when`(secureSettings.getIntForUser(
+                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
+                .thenReturn(1)
+
+        userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+
+        // Simulate a user switch event
+        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
+
+        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
+        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
+
+        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
+        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
+                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_WIPE).performClick()
+        testableLooper.processAllMessages()
+        assertEquals(1, uiEventLogger.numLogs())
+        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_WIPE.id, uiEventLogger.eventId(0))
+    }
+
+    @Test
+    fun testWipeGuest_continueButtonPressed_isLogged() {
+        val currentGuestUserRecord = UserSwitcherController.UserRecord(
+                guestInfo,
+                picture,
+                true /* guest */,
+                false /* current */,
+                false /* isAddUser */,
+                false /* isRestricted */,
+                true /* isSwitchToEnabled */)
+        `when`(userTracker.userId).thenReturn(guestId)
+        `when`(userTracker.userInfo).thenReturn(guestInfo)
+
+        // Simulate that guest user has already logged in
+        `when`(secureSettings.getIntForUser(
+                eq(GuestResumeSessionReceiver.SETTING_GUEST_HAS_LOGGED_IN), anyInt(), anyInt()))
+                .thenReturn(1)
+
+        userSwitcherController.onUserListItemClicked(currentGuestUserRecord)
+
+        // Simulate a user switch event
+        val intent = Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE, guestId)
+
+        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver)
+        userSwitcherController.mGuestResumeSessionReceiver.onReceive(context, intent)
+
+        assertNotNull(userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog)
+        userSwitcherController.mGuestResumeSessionReceiver.mNewSessionDialog
+                .getButton(GuestResumeSessionReceiver.ResetSessionDialog.BUTTON_DONTWIPE)
+                .performClick()
+        testableLooper.processAllMessages()
+        assertEquals(1, uiEventLogger.numLogs())
+        assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_CONTINUE.id, uiEventLogger.eventId(0))
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
index f5ccac3..516eb6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeConfigurationController.java
@@ -33,4 +33,9 @@
     @Override
     public void notifyThemeChanged() {
     }
+
+    @Override
+    public boolean isLayoutRtl() {
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index e336b6b..16645df 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -375,15 +375,38 @@
 
         try {
             FileChannel channel = getBlockOutputChannel();
+            // Format the data selectively.
+            //
+            // 1. write header, set length = 0
             int header_size = DIGEST_SIZE_BYTES + HEADER_SIZE;
             ByteBuffer buf = ByteBuffer.allocate(header_size);
             buf.put(new byte[DIGEST_SIZE_BYTES]);
             buf.putInt(PARTITION_TYPE_MARKER);
             buf.putInt(0);
+            buf.flip();
             channel.write(buf);
-            // corrupt the payload explicitly
+            channel.force(true);
+
+            // 2. corrupt the legacy FRP data explicitly
             int payload_size = (int) getBlockDeviceSize() - header_size;
-            buf = ByteBuffer.allocate(payload_size);
+            buf = ByteBuffer.allocate(payload_size
+                          - TEST_MODE_RESERVED_SIZE - FRP_CREDENTIAL_RESERVED_SIZE - 1);
+            channel.write(buf);
+            channel.force(true);
+
+            // 3. skip the test mode data and leave it unformat
+            //    This is for a feature that enables testing.
+            channel.position(channel.position() + TEST_MODE_RESERVED_SIZE);
+
+            // 4. wipe the FRP_CREDENTIAL explicitly
+            buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
+            channel.write(buf);
+            channel.force(true);
+
+            // 5. set unlock = 0 because it's a formatPartitionLocked
+            buf = ByteBuffer.allocate(FRP_CREDENTIAL_RESERVED_SIZE);
+            buf.put((byte)0);
+            buf.flip();
             channel.write(buf);
             channel.force(true);
         } catch (IOException e) {
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index ac43fbd..019e4ea 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -232,14 +232,14 @@
     public void onUserStarting(TargetUser user) {
         if (mCurrentUser == -1) {
             mCurrentUser = user.getUserIdentifier();
-            setGlobalRestriction();
+            mSensorPrivacyServiceImpl.userSwitching(-1, user.getUserIdentifier());
         }
     }
 
     @Override
     public void onUserSwitching(TargetUser from, TargetUser to) {
         mCurrentUser = to.getUserIdentifier();
-        setGlobalRestriction();
+        mSensorPrivacyServiceImpl.userSwitching(from.getUserIdentifier(), to.getUserIdentifier());
     }
 
     class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub implements
@@ -897,18 +897,22 @@
         public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) {
             enforceObserveSensorPrivacyPermission();
             synchronized (mLock) {
-                SparseArray<SensorState> states = mIndividualEnabled.get(userId);
-                if (states == null) {
-                    return false;
-                }
-                SensorState state = states.get(sensor);
-                if (state == null) {
-                    return false;
-                }
-                return state.mEnabled;
+                return isIndividualSensorPrivacyEnabledLocked(userId, sensor);
             }
         }
 
+        private boolean isIndividualSensorPrivacyEnabledLocked(int userId, int sensor) {
+            SparseArray<SensorState> states = mIndividualEnabled.get(userId);
+            if (states == null) {
+                return false;
+            }
+            SensorState state = states.get(sensor);
+            if (state == null) {
+                return false;
+            }
+            return state.mEnabled;
+        }
+
         /**
          * Returns the state of sensor privacy from persistent storage.
          */
@@ -1149,11 +1153,27 @@
                 ISensorPrivacyListener listener) {
             enforceObserveSensorPrivacyPermission();
             if (listener == null) {
-                throw new NullPointerException("listener cannot be null");
+                throw new IllegalArgumentException("listener cannot be null");
             }
             mHandler.addListener(userId, sensor, listener);
         }
 
+
+        /**
+         * Registers a listener to be notified when the sensor privacy state changes. The callback
+         * can be called if the user changes and the setting is different between the transitioning
+         * users.
+         */
+        @Override
+        public void addUserGlobalIndividualSensorPrivacyListener(int sensor,
+                ISensorPrivacyListener listener) {
+            enforceObserveSensorPrivacyPermission();
+            if (listener == null) {
+                throw new IllegalArgumentException("listener cannot be null");
+            }
+            mHandler.addUserGlobalListener(sensor, listener);
+        }
+
         /**
          * Unregisters a listener from sensor privacy state change notifications.
          */
@@ -1174,12 +1194,22 @@
                 ISensorPrivacyListener listener) {
             enforceObserveSensorPrivacyPermission();
             if (listener == null) {
-                throw new NullPointerException("listener cannot be null");
+                throw new IllegalArgumentException("listener cannot be null");
             }
             mHandler.removeListener(sensor, listener);
         }
 
         @Override
+        public void removeUserGlobalIndividualSensorPrivacyListener(int sensor,
+                ISensorPrivacyListener listener) {
+            enforceObserveSensorPrivacyPermission();
+            if (listener == null) {
+                throw new IllegalArgumentException("listener cannot be null");
+            }
+            mHandler.removeUserGlobalListener(sensor, listener);
+        }
+
+        @Override
         public void suppressIndividualSensorPrivacyReminders(int userId, int sensor,
                 IBinder token, boolean suppress) {
             enforceManageSensorPrivacyPermission();
@@ -1209,6 +1239,40 @@
             }
         }
 
+        private void userSwitching(int from, int to) {
+            boolean micState;
+            boolean camState;
+            boolean prevMicState;
+            boolean prevCamState;
+            synchronized (mLock) {
+                prevMicState = isIndividualSensorPrivacyEnabledLocked(from, MICROPHONE);
+                prevCamState = isIndividualSensorPrivacyEnabledLocked(from, CAMERA);
+                micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
+                camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
+            }
+            if (prevMicState != micState) {
+                mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
+                setGlobalRestriction(MICROPHONE, micState);
+            }
+            if (prevCamState != camState) {
+                mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
+                setGlobalRestriction(CAMERA, micState);
+            }
+        }
+
+        private void setGlobalRestriction(int sensor, boolean enabled) {
+            switch(sensor) {
+                case MICROPHONE:
+                    mAppOpsManagerInternal.setGlobalRestriction(OP_RECORD_AUDIO, enabled,
+                            mAppOpsRestrictionToken);
+                    break;
+                case CAMERA:
+                    mAppOpsManagerInternal.setGlobalRestriction(OP_CAMERA, enabled,
+                            mAppOpsRestrictionToken);
+                    break;
+            }
+        }
+
         /**
          * Remove a sensor use reminder suppression token.
          *
@@ -1454,7 +1518,12 @@
         @GuardedBy("mListenerLock")
         private final SparseArray<SparseArray<RemoteCallbackList<ISensorPrivacyListener>>>
                 mIndividualSensorListeners = new SparseArray<>();
-        private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients;
+        @GuardedBy("mListenerLock")
+        private final SparseArray<RemoteCallbackList<ISensorPrivacyListener>>
+                mUserGlobalIndividualSensorListeners = new SparseArray<>();
+        @GuardedBy("mListenerLock")
+        private final ArrayMap<ISensorPrivacyListener, Pair<DeathRecipient, Integer>>
+                mDeathRecipients;
         private final Context mContext;
 
         SensorPrivacyHandler(Looper looper, Context context) {
@@ -1479,18 +1548,22 @@
                             mSensorPrivacyServiceImpl));
         }
 
+        public void onUserGlobalSensorPrivacyChanged(int sensor, boolean enabled) {
+            sendMessage(PooledLambda.obtainMessage(
+                    SensorPrivacyHandler::handleUserGlobalSensorPrivacyChanged,
+                    this, sensor, enabled));
+        }
+
         public void addListener(ISensorPrivacyListener listener) {
             synchronized (mListenerLock) {
-                DeathRecipient deathRecipient = new DeathRecipient(listener);
-                mDeathRecipients.put(listener, deathRecipient);
-                mListeners.register(listener);
+                if (mListeners.register(listener)) {
+                    addDeathRecipient(listener);
+                }
             }
         }
 
         public void addListener(int userId, int sensor, ISensorPrivacyListener listener) {
             synchronized (mListenerLock) {
-                DeathRecipient deathRecipient = new DeathRecipient(listener);
-                mDeathRecipients.put(listener, deathRecipient);
                 SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
                         mIndividualSensorListeners.get(userId);
                 if (listenersForUser == null) {
@@ -1502,32 +1575,55 @@
                     listeners = new RemoteCallbackList<>();
                     listenersForUser.put(sensor, listeners);
                 }
-                listeners.register(listener);
+                if (listeners.register(listener)) {
+                    addDeathRecipient(listener);
+                }
+            }
+        }
+
+        public void addUserGlobalListener(int sensor, ISensorPrivacyListener listener) {
+            synchronized (mListenerLock) {
+                RemoteCallbackList<ISensorPrivacyListener> listeners =
+                        mUserGlobalIndividualSensorListeners.get(sensor);
+                if (listeners == null) {
+                    listeners = new RemoteCallbackList<>();
+                    mUserGlobalIndividualSensorListeners.put(sensor, listeners);
+                }
+                if (listeners.register(listener)) {
+                    addDeathRecipient(listener);
+                }
             }
         }
 
         public void removeListener(ISensorPrivacyListener listener) {
             synchronized (mListenerLock) {
-                DeathRecipient deathRecipient = mDeathRecipients.remove(listener);
-                if (deathRecipient != null) {
-                    deathRecipient.destroy();
+                if (mListeners.unregister(listener)) {
+                    removeDeathRecipient(listener);
                 }
-                mListeners.unregister(listener);
             }
         }
 
         public void removeListener(int sensor, ISensorPrivacyListener listener) {
             synchronized (mListenerLock) {
-                DeathRecipient deathRecipient = mDeathRecipients.remove(listener);
-                if (deathRecipient != null) {
-                    deathRecipient.destroy();
-                }
-
                 for (int i = 0, numUsers = mIndividualSensorListeners.size(); i < numUsers; i++) {
                     RemoteCallbackList callbacks =
                             mIndividualSensorListeners.valueAt(i).get(sensor);
                     if (callbacks != null) {
-                        callbacks.unregister(listener);
+                        if (callbacks.unregister(listener)) {
+                            removeDeathRecipient(listener);
+                        }
+                    }
+                }
+            }
+        }
+
+        public void removeUserGlobalListener(int sensor, ISensorPrivacyListener listener) {
+            synchronized (mListenerLock) {
+                RemoteCallbackList callbacks =
+                        mUserGlobalIndividualSensorListeners.get(sensor);
+                if (callbacks != null) {
+                    if (callbacks.unregister(listener)) {
+                        removeDeathRecipient(listener);
                     }
                 }
             }
@@ -1551,9 +1647,12 @@
             SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
                     mIndividualSensorListeners.get(userId);
 
-            setGlobalRestriction();
             if (userId == mCurrentUser) {
-                setGlobalRestriction();
+                mSensorPrivacyServiceImpl.setGlobalRestriction(sensor, enabled);
+            }
+
+            if (userId == mCurrentUser) {
+                onUserGlobalSensorPrivacyChanged(sensor, enabled);
             }
 
             if (listenersForUser == null) {
@@ -1563,16 +1662,42 @@
             if (listeners == null) {
                 return;
             }
-            final int count = listeners.beginBroadcast();
-            for (int i = 0; i < count; i++) {
-                ISensorPrivacyListener listener = listeners.getBroadcastItem(i);
-                try {
-                    listener.onSensorPrivacyChanged(enabled);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
+            try {
+                final int count = listeners.beginBroadcast();
+                for (int i = 0; i < count; i++) {
+                    ISensorPrivacyListener listener = listeners.getBroadcastItem(i);
+                    try {
+                        listener.onSensorPrivacyChanged(enabled);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
+                    }
                 }
+            } finally {
+                listeners.finishBroadcast();
             }
-            listeners.finishBroadcast();
+        }
+
+        public void handleUserGlobalSensorPrivacyChanged(int sensor, boolean enabled) {
+            RemoteCallbackList<ISensorPrivacyListener> listeners =
+                    mUserGlobalIndividualSensorListeners.get(sensor);
+
+            if (listeners == null) {
+                return;
+            }
+
+            try {
+                final int count = listeners.beginBroadcast();
+                for (int i = 0; i < count; i++) {
+                    ISensorPrivacyListener listener = listeners.getBroadcastItem(i);
+                    try {
+                        listener.onSensorPrivacyChanged(enabled);
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
+                    }
+                }
+            } finally {
+                listeners.finishBroadcast();
+            }
         }
 
         public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
@@ -1581,20 +1706,33 @@
                     SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
                     mSensorPrivacyServiceImpl, key, token));
         }
-    }
 
-    private void setGlobalRestriction() {
-        boolean camState =
-                mSensorPrivacyServiceImpl
-                        .isIndividualSensorPrivacyEnabled(mCurrentUser, CAMERA);
-        boolean micState =
-                mSensorPrivacyServiceImpl
-                        .isIndividualSensorPrivacyEnabled(mCurrentUser, MICROPHONE);
+        private void addDeathRecipient(ISensorPrivacyListener listener) {
+            Pair<DeathRecipient, Integer> deathRecipient = mDeathRecipients.get(listener);
+            if (deathRecipient == null) {
+                deathRecipient = new Pair<>(new DeathRecipient(listener), 1);
+            } else {
+                int newRefCount = deathRecipient.second + 1;
+                deathRecipient = new Pair<>(deathRecipient.first, newRefCount);
+            }
+            mDeathRecipients.put(listener, deathRecipient);
+        }
 
-        mAppOpsManagerInternal
-                .setGlobalRestriction(OP_CAMERA, camState, mAppOpsRestrictionToken);
-        mAppOpsManagerInternal
-                .setGlobalRestriction(OP_RECORD_AUDIO, micState, mAppOpsRestrictionToken);
+        private void removeDeathRecipient(ISensorPrivacyListener listener) {
+            Pair<DeathRecipient, Integer> deathRecipient = mDeathRecipients.get(listener);
+            if (deathRecipient == null) {
+                return;
+            } else {
+                int newRefCount = deathRecipient.second - 1;
+                if (newRefCount == 0) {
+                    mDeathRecipients.remove(listener);
+                    deathRecipient.first.destroy();
+                    return;
+                }
+                deathRecipient = new Pair<>(deathRecipient.first, newRefCount);
+            }
+            mDeathRecipients.put(listener, deathRecipient);
+        }
     }
 
     private final class DeathRecipient implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index abb8243..979a9ee 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -430,7 +430,13 @@
 
         final ApplicationExitInfo info = new ApplicationExitInfo(raw);
         final String[] packages = raw.getPackageList();
-        final int uid = raw.getPackageUid();
+        int uid = raw.getRealUid();
+        if (UserHandle.isIsolated(uid)) {
+            Integer k = mIsolatedUidRecords.getUidByIsolatedUid(uid);
+            if (k != null) {
+                uid = k;
+            }
+        }
         for (int i = 0; i < packages.length; i++) {
             addExitInfoInnerLocked(packages[i], uid, info);
         }
@@ -833,14 +839,14 @@
         pw.println(prefix + "package: " + packageName);
         int size = array.size();
         for (int i = 0; i < size; i++) {
-            pw.println(prefix + "  Historical Process Exit for userId=" + array.keyAt(i));
+            pw.println(prefix + "  Historical Process Exit for uid=" + array.keyAt(i));
             array.valueAt(i).dumpLocked(pw, prefix + "    ", sdf);
         }
     }
 
     @GuardedBy("mLock")
-    private void addExitInfoInnerLocked(String packageName, int userId, ApplicationExitInfo info) {
-        AppExitInfoContainer container = mData.get(packageName, userId);
+    private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
+        AppExitInfoContainer container = mData.get(packageName, uid);
         if (container == null) {
             container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
             if (UserHandle.isIsolated(info.getRealUid())) {
@@ -851,7 +857,7 @@
             } else {
                 container.mUid = info.getRealUid();
             }
-            mData.put(packageName, userId, container);
+            mData.put(packageName, uid, container);
         }
         container.addExitInfoLocked(info);
     }
@@ -997,7 +1003,7 @@
             info.setPackageList(app.getPackageList());
             info.setReason(ApplicationExitInfo.REASON_UNKNOWN);
             info.setStatus(0);
-            info.setImportance(procStateToImportance(app.mState.getSetProcState()));
+            info.setImportance(procStateToImportance(app.mState.getReportedProcState()));
             info.setPss(app.mProfile.getLastPss());
             info.setRss(app.mProfile.getLastRss());
             info.setTimestamp(timestamp);
diff --git a/services/core/java/com/android/server/am/LmkdConnection.java b/services/core/java/com/android/server/am/LmkdConnection.java
index 1ecb9eb..598f086 100644
--- a/services/core/java/com/android/server/am/LmkdConnection.java
+++ b/services/core/java/com/android/server/am/LmkdConnection.java
@@ -50,7 +50,7 @@
      * Used to hold the data for the statsd atoms logging
      * Must be in sync with statslog.h
      */
-    private static final int LMKD_REPLY_MAX_SIZE = 214;
+    private static final int LMKD_REPLY_MAX_SIZE = 222;
 
     // connection listener interface
     interface LmkdConnectionListener {
diff --git a/services/core/java/com/android/server/am/LmkdStatsReporter.java b/services/core/java/com/android/server/am/LmkdStatsReporter.java
index a8d0582..9158891 100644
--- a/services/core/java/com/android/server/am/LmkdStatsReporter.java
+++ b/services/core/java/com/android/server/am/LmkdStatsReporter.java
@@ -64,11 +64,14 @@
             final int freeMemKb = inputData.readInt();
             final int freeSwapKb = inputData.readInt();
             final int killReason = inputData.readInt();
+            final int thrashing = inputData.readInt();
+            final int maxThrashing = inputData.readInt();
             final String procName = inputData.readUTF();
 
             FrameworkStatsLog.write(FrameworkStatsLog.LMK_KILL_OCCURRED, uid, procName, oomScore,
                     pgFault, pgMajFault, rssInBytes, cacheInBytes, swapInBytes, processStartTimeNS,
-                    minOomScore, freeMemKb, freeSwapKb, mapKillReason(killReason));
+                    minOomScore, freeMemKb, freeSwapKb, mapKillReason(killReason), thrashing,
+                    maxThrashing);
         } catch (IOException e) {
             Slog.e(TAG, "Invalid buffer data. Failed to log LMK_KILL_OCCURRED");
             return;
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index 4f3438fe..ca31681 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -305,7 +305,7 @@
             }
             // Somehow our record doesn't match, remove it anyway
             Slog.w(TAG, "Stale " + proc + ", removing");
-            mPhantomProcesses.removeAt(index);
+            onPhantomProcessKilledLocked(proc);
         } else {
             // Is this one of the zombie processes we've known?
             final int idx = mZombiePhantomProcesses.indexOfKey(pid);
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 7520d88..dc6bcd8 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -1086,7 +1086,7 @@
         mCurRawAdj = mSetRawAdj = mCurAdj = mSetAdj = mVerifiedAdj = ProcessList.INVALID_ADJ;
         mCurCapability = mSetCapability = PROCESS_CAPABILITY_NONE;
         mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
-        mCurProcState = mRepProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
+        mCurProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
                 PROCESS_STATE_NONEXISTENT;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 6b7787a..4973d45 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1103,9 +1103,8 @@
         }
 
         public boolean isAdvancedCoexLogicEnabled(Context context) {
-            return (Build.IS_USERDEBUG || Build.IS_ENG)
-                    && Settings.Secure.getInt(context.getContentResolver(),
-                    CoexCoordinator.SETTING_ENABLE_NAME, 0) != 0;
+            return Settings.Secure.getInt(context.getContentResolver(),
+                    CoexCoordinator.SETTING_ENABLE_NAME, 1) != 0;
         }
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index 6463e04..013c74d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -70,6 +70,7 @@
     private final LockoutTracker mLockoutTracker;
     private final boolean mIsRestricted;
     private final boolean mAllowBackgroundAuthentication;
+    private final boolean mIsKeyguardBypassEnabled;
 
     protected final long mOperationId;
 
@@ -97,7 +98,7 @@
             int cookie, boolean requireConfirmation, int sensorId, boolean isStrongBiometric,
             int statsModality, int statsClient, @Nullable TaskStackListener taskStackListener,
             @NonNull LockoutTracker lockoutTracker, boolean allowBackgroundAuthentication,
-            boolean shouldVibrate) {
+            boolean shouldVibrate, boolean isKeyguardBypassEnabled) {
         super(context, lazyDaemon, token, listener, targetUserId, owner, cookie, sensorId,
                 shouldVibrate, statsModality, BiometricsProtoEnums.ACTION_AUTHENTICATE,
                 statsClient);
@@ -110,6 +111,7 @@
         mLockoutTracker = lockoutTracker;
         mIsRestricted = restricted;
         mAllowBackgroundAuthentication = allowBackgroundAuthentication;
+        mIsKeyguardBypassEnabled = isKeyguardBypassEnabled;
     }
 
     public @LockoutTracker.LockoutMode int handleFailedAttempt(int userId) {
@@ -394,6 +396,14 @@
         return mState;
     }
 
+    /**
+     * @return true if the client supports bypass (e.g. passive auth such as face), and if it's
+     * enabled by the user.
+     */
+    public boolean isKeyguardBypassEnabled() {
+        return mIsKeyguardBypassEnabled;
+    }
+
     @Override
     public int getProtoEnum() {
         return BiometricsProto.CM_AUTHENTICATE;
diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
index f97cb8a..b576673 100644
--- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java
@@ -26,7 +26,6 @@
 import android.os.Looper;
 import android.util.Slog;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.biometrics.sensors.BiometricScheduler.SensorType;
 import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -242,6 +241,11 @@
                     callback.sendHapticFeedback();
                     callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
                     callback.handleLifecycleAfterAuth();
+                } else {
+                    // Capacitive fingerprint sensor (or other)
+                    callback.sendHapticFeedback();
+                    callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */);
+                    callback.handleLifecycleAfterAuth();
                 }
             }
         } else {
@@ -268,11 +272,18 @@
                 AuthenticationClient<?> udfps = mClientMap.getOrDefault(SENSOR_TYPE_UDFPS, null);
                 AuthenticationClient<?> face = mClientMap.getOrDefault(SENSOR_TYPE_FACE, null);
                 if (isCurrentFaceAuth(client)) {
-                    // UDFPS should still be running in this case, do not vibrate. However, we
-                    // should notify the callback and finish the client, so that Keyguard and
-                    // BiometricScheduler do not get stuck.
-                    Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps);
-                    callback.handleLifecycleAfterAuth();
+                    if (isUdfpsActivelyAuthing(udfps)) {
+                        // UDFPS should still be running in this case, do not vibrate. However, we
+                        // should notify the callback and finish the client, so that Keyguard and
+                        // BiometricScheduler do not get stuck.
+                        Slog.d(TAG, "Face rejected in multi-sensor auth, udfps: " + udfps);
+                        callback.handleLifecycleAfterAuth();
+                    } else {
+                        // UDFPS is not actively authenticating (finger not touching, already
+                        // rejected, etc).
+                        callback.sendHapticFeedback();
+                        callback.handleLifecycleAfterAuth();
+                    }
                 } else if (isCurrentUdfps(client)) {
                     // Face should either be running, or have already finished
                     SuccessfulAuth auth = popSuccessfulFaceAuthIfExists(currentTimeMillis);
diff --git a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
index c8867ea..b4c82f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/LoggableMonitor.java
@@ -48,6 +48,64 @@
     private boolean mLightSensorEnabled = false;
     private boolean mShouldLogMetrics = true;
 
+    /**
+     * Probe for loggable attributes that can be continuously monitored, such as ambient light.
+     *
+     * Disable probes when the sensors are in states that are not interesting for monitoring
+     * purposes to save power.
+     */
+    protected interface Probe {
+        /** Ensure the probe is actively sampling for new data. */
+        void enable();
+        /** Stop sampling data. */
+        void disable();
+    }
+
+    /**
+     * Client monitor callback that exposes a probe.
+     *
+     * Disables the probe when the operation completes.
+     */
+    protected static class CallbackWithProbe<T extends Probe>
+            implements BaseClientMonitor.Callback {
+        private final boolean mStartWithClient;
+        private final T mProbe;
+
+        public CallbackWithProbe(@NonNull T probe, boolean startWithClient) {
+            mProbe = probe;
+            mStartWithClient = startWithClient;
+        }
+
+        @Override
+        public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
+            if (mStartWithClient) {
+                mProbe.enable();
+            }
+        }
+
+        @Override
+        public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
+            mProbe.disable();
+        }
+
+        @NonNull
+        public T getProbe() {
+            return mProbe;
+        }
+    }
+
+    private class ALSProbe implements Probe {
+        @Override
+        public void enable() {
+            setLightSensorLoggingEnabled(getAmbientLightSensor(mSensorManager));
+        }
+
+        @Override
+        public void disable() {
+            setLightSensorLoggingEnabled(null);
+        }
+    }
+
     // report only the most recent value
     // consider com.android.server.display.utils.AmbientFilter or similar if need arises
     private volatile float mLastAmbientLux = 0;
@@ -285,21 +343,17 @@
         return latency;
     }
 
-    /** Get a callback to start/stop ALS capture when client runs. */
+    /**
+     * Get a callback to start/stop ALS capture when client runs.
+     *
+     * If the probe should not run for the entire operation, do not set startWithClient and
+     * start/stop the problem when needed.
+     *
+     * @param startWithClient if probe should start automatically when the operation starts.
+     */
     @NonNull
-    protected BaseClientMonitor.Callback createALSCallback() {
-        return new BaseClientMonitor.Callback() {
-            @Override
-            public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
-                setLightSensorLoggingEnabled(getAmbientLightSensor(mSensorManager));
-            }
-
-            @Override
-            public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
-                    boolean success) {
-                setLightSensorLoggingEnabled(null);
-            }
-        };
+    protected CallbackWithProbe<Probe> createALSCallback(boolean startWithClient) {
+        return new CallbackWithProbe<>(new ALSProbe(), startWithClient);
     }
 
     /** The sensor to use for ALS logging. */
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 219e063..12d6b08 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -251,7 +251,8 @@
 
         @Override // Binder call
         public void authenticate(final IBinder token, final long operationId, int userId,
-                final IFaceServiceReceiver receiver, final String opPackageName) {
+                final IFaceServiceReceiver receiver, final String opPackageName,
+                boolean isKeyguardBypassEnabled) {
             Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
 
             // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
@@ -275,7 +276,7 @@
             provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
                     0 /* cookie */,
                     new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
-                    statsClient, isKeyguard);
+                    statsClient, isKeyguard, isKeyguardBypassEnabled);
         }
 
         @Override // Binder call
@@ -318,10 +319,12 @@
                 return;
             }
 
+            final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
             final boolean restricted = true; // BiometricPrompt is always restricted
             provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
                     new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
-                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication);
+                    BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
+                    isKeyguardBypassEnabled);
         }
 
         @Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 1d2ac3b..93ab1b6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -110,7 +110,7 @@
     void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
             int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication);
+            boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
 
     void cancelAuthentication(int sensorId, @NonNull IBinder token);
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 35c1745..f7fd8d0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -69,11 +69,13 @@
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
             boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
-            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication) {
+            @NonNull LockoutCache lockoutCache, boolean allowBackgroundAuthentication,
+            boolean isKeyguardBypassEnabled) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
                 BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
-                lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */);
+                lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+                isKeyguardBypassEnabled);
         mUsageStats = usageStats;
         mLockoutCache = lockoutCache;
         mNotificationManager = context.getSystemService(NotificationManager.class);
@@ -98,7 +100,7 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(createALSCallback(), callback);
+        return new CompositeCallback(createALSCallback(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
index 55c987a..a806277 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceEnrollClient.java
@@ -109,7 +109,8 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(mPreviewHandleDeleterCallback, createALSCallback(), callback);
+        return new CompositeCallback(mPreviewHandleDeleterCallback,
+                createALSCallback(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 5c24108..718b9da 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -378,7 +378,7 @@
     public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
             int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
             @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication) {
+            boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
         mHandler.post(() -> {
             final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
             final FaceAuthenticationClient client = new FaceAuthenticationClient(
@@ -386,7 +386,7 @@
                     operationId, restricted, opPackageName, cookie,
                     false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
                     mUsageStats, mSensors.get(sensorId).getLockoutCache(),
-                    allowBackgroundAuthentication);
+                    allowBackgroundAuthentication, isKeyguardBypassEnabled);
             scheduleForSensor(sensorId, client);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index da4ad86..d05333d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -622,7 +622,7 @@
     public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
             int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
             @NonNull String opPackageName, boolean restricted, int statsClient,
-            boolean allowBackgroundAuthentication) {
+            boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
         mHandler.post(() -> {
             scheduleUpdateActiveUserWithoutHandler(userId);
 
@@ -630,7 +630,8 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
                     mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
                     cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
-                    statsClient, mLockoutTracker, mUsageStats, allowBackgroundAuthentication);
+                    statsClient, mLockoutTracker, mUsageStats, allowBackgroundAuthentication,
+                    isKeyguardBypassEnabled);
             mScheduler.scheduleClientMonitor(client);
         });
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index e65245b..c33b957 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -61,11 +61,13 @@
             @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
             boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
             boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
-            @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication) {
+            @NonNull UsageStats usageStats, boolean allowBackgroundAuthentication,
+            boolean isKeyguardBypassEnabled) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
                 BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
-                lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */);
+                lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
+                isKeyguardBypassEnabled);
         mUsageStats = usageStats;
 
         final Resources resources = getContext().getResources();
@@ -88,7 +90,7 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(createALSCallback(), callback);
+        return new CompositeCallback(createALSCallback(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
index 455d6f8..80828cced 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceEnrollClient.java
@@ -69,7 +69,7 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(createALSCallback(), callback);
+        return new CompositeCallback(createALSCallback(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 14d1822..99e6e62 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -55,6 +55,7 @@
     @NonNull private final LockoutCache mLockoutCache;
     @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
+    @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
 
     @Nullable private ICancellationSignal mCancellationSignal;
     private boolean mIsPointerDown;
@@ -71,10 +72,12 @@
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
                 cookie, requireConfirmation, sensorId, isStrongBiometric,
                 BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
-                lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */);
+                lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+                false /* isKeyguardBypassEnabled */);
         mLockoutCache = lockoutCache;
         mUdfpsOverlayController = udfpsOverlayController;
         mSensorProps = sensorProps;
+        mALSProbeCallback = createALSCallback(false /* startWithClient */);
     }
 
     @Override
@@ -92,7 +95,7 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(createALSCallback(), callback);
+        return new CompositeCallback(mALSProbeCallback, callback);
     }
 
     @Override
@@ -170,7 +173,9 @@
         try {
             mIsPointerDown = true;
             mState = STATE_STARTED;
+            mALSProbeCallback.getProbe().enable();
             getFreshDaemon().onPointerDown(0 /* pointerId */, x, y, minor, major);
+
             if (getListener() != null) {
                 getListener().onUdfpsPointerDown(getSensorId());
             }
@@ -184,7 +189,9 @@
         try {
             mIsPointerDown = false;
             mState = STATE_STARTED_PAUSED;
+            mALSProbeCallback.getProbe().disable();
             getFreshDaemon().onPointerUp(0 /* pointerId */);
+
             if (getListener() != null) {
                 getListener().onUdfpsPointerUp(getSensorId());
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index e8200af..c420c5c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -84,7 +84,7 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(createALSCallback(), callback);
+        return new CompositeCallback(createALSCallback(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 9347244..7558d15 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -54,6 +54,7 @@
     private final LockoutFrameworkImpl mLockoutFrameworkImpl;
     @Nullable private final IUdfpsOverlayController mUdfpsOverlayController;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
+    @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
 
     private boolean mIsPointerDown;
 
@@ -70,10 +71,12 @@
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, isStrongBiometric,
                 BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
-                lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */);
+                lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
+                false /* isKeyguardBypassEnabled */);
         mLockoutFrameworkImpl = lockoutTracker;
         mUdfpsOverlayController = udfpsOverlayController;
         mSensorProps = sensorProps;
+        mALSProbeCallback = createALSCallback(false /* startWithClient */);
     }
 
     @Override
@@ -91,7 +94,7 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(createALSCallback(), callback);
+        return new CompositeCallback(mALSProbeCallback, callback);
     }
 
     @Override
@@ -188,7 +191,9 @@
     public void onPointerDown(int x, int y, float minor, float major) {
         mIsPointerDown = true;
         mState = STATE_STARTED;
+        mALSProbeCallback.getProbe().enable();
         UdfpsHelper.onFingerDown(getFreshDaemon(), x, y, minor, major);
+
         if (getListener() != null) {
             try {
                 getListener().onUdfpsPointerDown(getSensorId());
@@ -202,7 +207,9 @@
     public void onPointerUp() {
         mIsPointerDown = false;
         mState = STATE_STARTED_PAUSED;
+        mALSProbeCallback.getProbe().disable();
         UdfpsHelper.onFingerUp(getFreshDaemon());
+
         if (getListener() != null) {
             try {
                 getListener().onUdfpsPointerUp(getSensorId());
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 250e132..dc70534 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -77,7 +77,7 @@
     @NonNull
     @Override
     protected Callback wrapCallbackForStart(@NonNull Callback callback) {
-        return new CompositeCallback(createALSCallback(), callback);
+        return new CompositeCallback(createALSCallback(true /* startWithClient */), callback);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 1edede5..59f536c 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -778,7 +778,7 @@
         setUpAutoBrightness(mContext.getResources(), mHandler);
         reloadReduceBrightColours();
         mHbmController.resetHbmData(info.width, info.height, token,
-                mDisplayDeviceConfig.getHighBrightnessModeData(), mBrightnessSetting);
+                mDisplayDeviceConfig.getHighBrightnessModeData());
     }
 
     private void sendUpdatePowerState() {
@@ -968,7 +968,6 @@
         final boolean mustNotify;
         final int previousPolicy;
         boolean mustInitialize = false;
-        boolean shouldSaveBrightnessInfo = true;
         int brightnessAdjustmentFlags = 0;
         mBrightnessReasonTemp.set(null);
         synchronized (mLock) {
@@ -1099,7 +1098,6 @@
         if (state == Display.STATE_OFF) {
             brightnessState = PowerManager.BRIGHTNESS_OFF_FLOAT;
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_SCREEN_OFF);
-            shouldSaveBrightnessInfo = false;
         }
 
         // Always use the VR brightness when in the VR state.
@@ -1217,6 +1215,10 @@
                 brightnessAdjustmentFlags = 0;
             }
         } else {
+            // Any non-auto-brightness values such as override or temporary should still be subject
+            // to clamping so that they don't go beyond the current max as specified by HBM
+            // Controller.
+            brightnessState = clampScreenBrightness(brightnessState);
             mAppliedAutoBrightness = false;
             brightnessAdjustmentFlags = 0;
         }
@@ -1224,9 +1226,8 @@
         // Use default brightness when dozing unless overridden.
         if ((Float.isNaN(brightnessState))
                 && Display.isDozeState(state)) {
-            brightnessState = mScreenBrightnessDozeConfig;
+            brightnessState = clampScreenBrightness(mScreenBrightnessDozeConfig);
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
-            shouldSaveBrightnessInfo = false;
         }
 
         // Apply manual brightness.
@@ -1241,12 +1242,13 @@
             mBrightnessReasonTemp.setReason(BrightnessReason.REASON_MANUAL);
         }
 
-        // Save out the brightness info now that the brightness state for this iteration has been
-        // finalized and before we send out notifications about the brightness changing.
-        if (shouldSaveBrightnessInfo) {
-            saveBrightnessInfo(brightnessState);
-
-        }
+        // The current brightness to use has been calculated at this point (minus the adjustments
+        // like low-power and dim), and HbmController should be notified so that it can accurately
+        // calculate HDR or HBM levels. We specifically do it here instead of having HbmController
+        // listen to the brightness setting because certain brightness sources (just as an app
+        // override) are not saved to the setting, but should be reflected in HBM
+        // calculations.
+        mHbmController.onBrightnessChanged(brightnessState);
 
         if (updateScreenBrightnessSetting) {
             // Tell the rest of the system about the new brightness in case we had to change it
@@ -1257,6 +1259,10 @@
             putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
         }
 
+        // We save the brightness info *after* the brightness setting has been changed so that
+        // the brightness info reflects the latest value.
+        saveBrightnessInfo(getScreenBrightnessSetting());
+
         // Apply dimming by at least some minimum amount when user activity
         // timeout is about to expire.
         if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1533,7 +1539,7 @@
                     mHandler.post(mOnBrightnessChangeRunnable);
                     // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                     mAutomaticBrightnessController.update();
-                }, mContext, mBrightnessSetting);
+                }, mContext);
     }
 
     private void blockScreenOn() {
@@ -2336,7 +2342,7 @@
             try {
                 // TODO(brightnessfloat): change BatteryStats to use float
                 mBatteryStats.noteScreenBrightness(BrightnessSynchronizer.brightnessFloatToInt(
-                        brightness));
+                        brightness, null));
             } catch (RemoteException e) {
                 // same process
             }
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 645131c..2791f6a 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -37,7 +37,6 @@
 import android.view.SurfaceControlHdrLayerInfoListener;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.display.BrightnessSetting.BrightnessSettingListener;
 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
 import com.android.server.display.DisplayManagerService.Clock;
 
@@ -70,7 +69,6 @@
     private final Context mContext;
     private final SettingsObserver mSettingsObserver;
     private final Injector mInjector;
-    private final BrightnessSettingListener mBrightnessSettingListener = this::onBrightnessChanged;
 
     private HdrListener mHdrListener;
     private HighBrightnessModeData mHbmData;
@@ -86,7 +84,6 @@
     private boolean mIsBlockedByLowPowerMode = false;
     private int mWidth;
     private int mHeight;
-    private BrightnessSetting mBrightnessSetting;
     private float mAmbientLux;
 
     /**
@@ -103,30 +100,30 @@
 
     HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
             float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
-            Runnable hbmChangeCallback, Context context, BrightnessSetting brightnessSetting) {
+            Runnable hbmChangeCallback, Context context) {
         this(new Injector(), handler, width, height, displayToken, brightnessMin, brightnessMax,
-                hbmData, hbmChangeCallback, context, brightnessSetting);
+                hbmData, hbmChangeCallback, context);
     }
 
     @VisibleForTesting
     HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
             IBinder displayToken, float brightnessMin, float brightnessMax,
             HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
-            Context context, BrightnessSetting brightnessSetting) {
+            Context context) {
         mInjector = injector;
         mContext = context;
         mClock = injector.getClock();
         mHandler = handler;
+        mBrightness = brightnessMin;
         mBrightnessMin = brightnessMin;
         mBrightnessMax = brightnessMax;
-        mBrightness = brightnessSetting.getBrightness();
         mHbmChangeCallback = hbmChangeCallback;
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
         mRecalcRunnable = this::recalculateTimeAllowance;
         mHdrListener = new HdrListener();
 
-        resetHbmData(width, height, displayToken, hbmData, brightnessSetting);
+        resetHbmData(width, height, displayToken, hbmData);
     }
 
     void setAutoBrightnessEnabled(boolean isEnabled) {
@@ -185,7 +182,6 @@
         }
     }
 
-    @VisibleForTesting
     void onBrightnessChanged(float brightness) {
         if (!deviceSupportsHbm()) {
             return;
@@ -224,12 +220,11 @@
         mSettingsObserver.stopObserving();
     }
 
-    void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData,
-            BrightnessSetting brightnessSetting) {
+    void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData) {
         mWidth = width;
         mHeight = height;
         mHbmData = hbmData;
-        resetBrightnessSetting(brightnessSetting);
+
         unregisterHdrListener();
         mSkinThermalStatusObserver.stopObserving();
         mSettingsObserver.stopObserving();
@@ -261,9 +256,12 @@
         pw.println("  mBrightness=" + mBrightness);
         pw.println("  mCurrentMin=" + getCurrentBrightnessMin());
         pw.println("  mCurrentMax=" + getCurrentBrightnessMax());
-        pw.println("  mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode));
+        pw.println("  mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
+                + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
+                ? "(" + getHdrBrightnessValue() + ")" : ""));
         pw.println("  mHbmData=" + mHbmData);
-        pw.println("  mAmbientLux=" + mAmbientLux);
+        pw.println("  mAmbientLux=" + mAmbientLux
+                + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)"));
         pw.println("  mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
         pw.println("  mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
         pw.println("  mIsHdrLayerPresent=" + mIsHdrLayerPresent);
@@ -301,16 +299,6 @@
         return event.startTimeMillis;
     }
 
-    private void resetBrightnessSetting(BrightnessSetting brightnessSetting) {
-        if (mBrightnessSetting != null) {
-            mBrightnessSetting.unregisterListener(mBrightnessSettingListener);
-        }
-        mBrightnessSetting = brightnessSetting;
-        if (mBrightnessSetting != null) {
-            mBrightnessSetting.registerListener(mBrightnessSettingListener);
-        }
-    }
-
     private boolean isCurrentlyAllowed() {
         // Returns true if HBM is allowed (above the ambient lux threshold) and there's still
         // time within the current window for additional HBM usage. We return false if there is an
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index f953cc8..ff143bf 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -601,12 +601,6 @@
                             && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
                         mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
                     }
-                    if (res.getBoolean(
-                            com.android.internal.R.bool.config_maskMainBuiltInDisplayCutout)) {
-                        mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
-                    }
-                    mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
-                            mInfo.width, mInfo.height);
                     mInfo.roundedCorners = RoundedCorners.fromResources(
                             res, mInfo.width, mInfo.height);
                 } else {
@@ -620,6 +614,12 @@
                     }
                 }
 
+                if (DisplayCutout.getMaskBuiltInDisplayCutout(res, mInfo.uniqueId)) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
+                }
+                mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+                        mInfo.uniqueId, mInfo.width, mInfo.height);
+
                 if (mStaticDisplayInfo.isInternal) {
                     mInfo.type = Display.TYPE_INTERNAL;
                     mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
@@ -795,11 +795,12 @@
                             mBacklightAdapter.setBacklight(sdrBacklight, sdrNits, backlight, nits);
                             Trace.traceCounter(Trace.TRACE_TAG_POWER,
                                     "ScreenBrightness",
-                                    BrightnessSynchronizer.brightnessFloatToInt(brightnessState));
+                                    BrightnessSynchronizer.brightnessFloatToInt(
+                                            brightnessState, null));
                             Trace.traceCounter(Trace.TRACE_TAG_POWER,
                                     "SdrScreenBrightness",
                                     BrightnessSynchronizer.brightnessFloatToInt(
-                                            sdrBrightnessState));
+                                            sdrBrightnessState, null));
                         } finally {
                             Trace.traceEnd(Trace.TRACE_TAG_POWER);
                         }
diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
index 91f14de..bebe6ed 100644
--- a/services/core/java/com/android/server/lights/LightsService.java
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -293,7 +293,7 @@
                             + ": brightness=" + brightness);
                     return;
                 }
-                int brightnessInt = BrightnessSynchronizer.brightnessFloatToInt(brightness);
+                int brightnessInt = BrightnessSynchronizer.brightnessFloatToInt(brightness, null);
                 int color = brightnessInt & 0x000000ff;
                 color = 0xff000000 | (color << 16) | (color << 8) | color;
                 setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index fb4d96e..d1df989 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3020,7 +3020,8 @@
 
     @Override
     public void onKeyguardOccludedChangedLw(boolean occluded) {
-        if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()) {
+        if (mKeyguardDelegate != null && mKeyguardDelegate.isShowing()
+                && !WindowManagerService.sEnableShellTransitions) {
             mPendingKeyguardOccluded = occluded;
             mKeyguardOccludedChanged = true;
         } else {
@@ -4246,6 +4247,7 @@
                                     pmWakeReason)) + ")");
         }
 
+        mActivityTaskManagerInternal.notifyWakingUp();
         mDefaultDisplayPolicy.setAwake(true);
 
         // Since goToSleep performs these functions synchronously, we must
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index d95e826..cdab91b 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -22,8 +22,8 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -133,7 +133,7 @@
     /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
     void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsState requestedState, String packageName);
+            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName);
 
     /** @see com.android.internal.statusbar.IStatusBar#showTransient */
     void showTransient(int displayId, @InternalInsetsType int[] types);
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 47fdc4e..ff7e903 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -59,8 +59,8 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -527,13 +527,14 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsState requestedState, String packageName) {
+                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                String packageName) {
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
-                    navbarColorManagedByIme, behavior, requestedState, packageName);
+                    navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
             if (mBar != null) {
                 try {
                     mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                            navbarColorManagedByIme, behavior, requestedState, packageName);
+                            navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
                 } catch (RemoteException ex) { }
             }
         }
@@ -1110,7 +1111,7 @@
         private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
         private boolean mNavbarColorManagedByIme = false;
         private @Behavior int mBehavior;
-        private InsetsState mRequestedState = new InsetsState();
+        private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
         private String mPackageName = "none";
         private int mDisabled1 = 0;
         private int mDisabled2 = 0;
@@ -1121,12 +1122,13 @@
 
         private void setBarAttributes(@Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsState requestedState, String packageName) {
+                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                String packageName) {
             mAppearance = appearance;
             mAppearanceRegions = appearanceRegions;
             mNavbarColorManagedByIme = navbarColorManagedByIme;
             mBehavior = behavior;
-            mRequestedState = requestedState;
+            mRequestedVisibilities = requestedVisibilities;
             mPackageName = packageName;
         }
 
@@ -1247,7 +1249,7 @@
                     state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
                     state.mImeBackDisposition, state.mShowImeSwitcher,
                     gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
-                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedState,
+                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
                     state.mPackageName, transientBarTypes);
         }
     }
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 3c0a05b..450257f 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -195,6 +195,7 @@
 
     @VisibleForTesting(visibility = Visibility.PRIVATE)
     static final int SAFEMODE_TIMEOUT_SECONDS = 30;
+    private static final int SAFEMODE_TIMEOUT_SECONDS_TEST_MODE = 10;
 
     private interface EventInfo {}
 
@@ -1082,7 +1083,9 @@
                 createScheduledAlarm(
                         SAFEMODE_TIMEOUT_ALARM,
                         delayedMessage,
-                        TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
+                        mVcnContext.isInTestMode()
+                                ? TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS_TEST_MODE)
+                                : TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
     }
 
     private void cancelSafeModeAlarm() {
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index b1163c4..005a62a 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -936,9 +936,11 @@
         // This will avoid any races with other operations that modify the ActivityRecord.
         final TransitionInfoSnapshot infoSnapshot = new TransitionInfoSnapshot(info);
         if (info.isInterestingToLoggerAndObserver()) {
+            final long timestamp = info.mTransitionStartTimeNs;
+            final long uptime = info.mTransitionDeviceUptimeMs;
+            final int transitionDelay = info.mCurrentTransitionDelayMs;
             mLoggerHandler.post(() -> logAppTransition(
-                    info.mTransitionDeviceUptimeMs, info.mCurrentTransitionDelayMs,
-                    infoSnapshot, isHibernating));
+                    timestamp, uptime, transitionDelay, infoSnapshot, isHibernating));
         }
         mLoggerHandler.post(() -> logAppDisplayed(infoSnapshot));
         if (info.mPendingFullyDrawn != null) {
@@ -949,8 +951,8 @@
     }
 
     // This gets called on another thread without holding the activity manager lock.
-    private void logAppTransition(long transitionDeviceUptimeMs, int currentTransitionDelayMs,
-            TransitionInfoSnapshot info, boolean isHibernating) {
+    private void logAppTransition(long transitionStartTimeNs, long transitionDeviceUptimeMs,
+            int currentTransitionDelayMs, TransitionInfoSnapshot info, boolean isHibernating) {
         final LogMaker builder = new LogMaker(APP_TRANSITION);
         builder.setPackageName(info.packageName);
         builder.setType(info.type);
@@ -1001,7 +1003,7 @@
                 info.launchedActivityName,
                 info.launchedActivityLaunchedFromPackage,
                 isInstantApp,
-                transitionDeviceUptimeMs,
+                0 /* deprecated transitionDeviceUptimeMs */,
                 info.reason,
                 currentTransitionDelayMs,
                 info.startingWindowDelayMs,
@@ -1015,7 +1017,8 @@
                 isHibernating,
                 isIncremental,
                 isLoading,
-                info.launchedActivityName.hashCode());
+                info.launchedActivityName.hashCode(),
+                TimeUnit.NANOSECONDS.toMillis(transitionStartTimeNs));
 
         if (DEBUG_METRICS) {
             Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 00720cf..5970f62 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5154,6 +5154,12 @@
     void notifyAppStopped() {
         ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppStopped: %s", this);
         mAppStopped = true;
+        // This is to fix the edge case that auto-enter-pip is finished in Launcher but app calls
+        // setAutoEnterEnabled(false) and transitions to STOPPED state, see b/191930787.
+        // Clear any surface transactions and content overlay in this case.
+        if (task != null && task.mLastRecentsAnimationTransaction != null) {
+            task.clearLastRecentsAnimationTransaction(true /* forceRemoveOverlay */);
+        }
         // Reset the last saved PiP snap fraction on app stop.
         mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
         mDisplayContent.mUnknownAppVisibilityController.appRemovedOrHidden(this);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 1759cde..5174a38 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -621,4 +621,7 @@
      */
     public abstract boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
             String callingPackage);
+
+    /** Called when the device is waking up */
+    public abstract void notifyWakingUp();
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 381d695..83c83e7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -64,6 +64,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManager.TRANSIT_WAKE;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -6506,6 +6507,13 @@
             return ActivityTaskManagerService.this.hasSystemAlertWindowPermission(callingUid,
                     callingPid, callingPackage);
         }
+
+        @Override
+        public void notifyWakingUp() {
+            // Start a transition for waking. This is needed for showWhenLocked activities.
+            getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */,
+                    null /* trigger */, mRootWindowContainer.getDefaultDisplay());
+        }
     }
 
     final class PackageConfigurationUpdaterImpl implements
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c5fb34c..369e58c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -61,6 +61,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
@@ -201,6 +202,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.MagnificationSpec;
 import android.view.PrivacyIndicatorBounds;
 import android.view.RemoteAnimationDefinition;
@@ -504,11 +506,6 @@
     WindowState mCurrentFocus = null;
 
     /**
-     * The last focused window that we've notified the client that the focus is changed.
-     */
-    WindowState mLastFocus = null;
-
-    /**
      * The foreground app of this display. Windows below this app cannot be the focused window. If
      * the user taps on the area outside of the task of the focused app, we will notify AM about the
      * new task the user wants to interact with.
@@ -1243,12 +1240,6 @@
                 // removing from parent.
                 token.getParent().removeChild(token);
             }
-            if (token.hasChild(prevDc.mLastFocus)) {
-                // If the reparent window token contains previous display's last focus window, means
-                // it will end up to gain window focus on the target display, so it should not be
-                // notified that it lost focus from the previous display.
-                prevDc.mLastFocus = null;
-            }
         }
 
         addWindowToken(token.token, token);
@@ -3203,9 +3194,6 @@
         pw.print(prefix); pw.print("mLayoutSeq="); pw.println(mLayoutSeq);
 
         pw.print("  mCurrentFocus="); pw.println(mCurrentFocus);
-        if (mLastFocus != mCurrentFocus) {
-            pw.print("  mLastFocus="); pw.println(mLastFocus);
-        }
         pw.print("  mFocusedApp="); pw.println(mFocusedApp);
         if (mFixedRotationLaunchingApp != null) {
             pw.println("  mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
@@ -3436,8 +3424,6 @@
             }
         }
 
-        onWindowFocusChanged(oldFocus, newFocus);
-
         int focusChanged = getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
 
         if (imWindowChanged && oldFocus != mInputMethodWindow) {
@@ -3488,7 +3474,6 @@
                     mWmService.mAccessibilityController));
         }
 
-        mLastFocus = mCurrentFocus;
         return true;
     }
 
@@ -3496,20 +3481,6 @@
         accessibilityController.onWindowFocusChangedNot(getDisplayId());
     }
 
-    private static void onWindowFocusChanged(WindowState oldFocus, WindowState newFocus) {
-        final Task focusedTask = newFocus != null ? newFocus.getTask() : null;
-        final Task unfocusedTask = oldFocus != null ? oldFocus.getTask() : null;
-        if (focusedTask == unfocusedTask) {
-            return;
-        }
-        if (focusedTask != null) {
-            focusedTask.onWindowFocusChanged(true /* hasFocus */);
-        }
-        if (unfocusedTask != null) {
-            unfocusedTask.onWindowFocusChanged(false /* hasFocus */);
-        }
-    }
-
     /**
      * Set the new focused app to this display.
      *
@@ -3533,7 +3504,14 @@
         }
         ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "setFocusedApp %s displayId=%d Callers=%s",
                 newFocus, getDisplayId(), Debug.getCallers(4));
+        final Task oldTask = mFocusedApp != null ? mFocusedApp.getTask() : null;
+        final Task newTask = newFocus != null ? newFocus.getTask() : null;
         mFocusedApp = newFocus;
+        if (oldTask != newTask) {
+            if (oldTask != null) oldTask.onAppFocusChanged(false);
+            if (newTask != null) newTask.onAppFocusChanged(true);
+        }
+
         getInputMonitor().setFocusedAppLw(newFocus);
         updateTouchExcludeRegion();
         return true;
@@ -4229,7 +4207,6 @@
         if (DEBUG_INPUT_METHOD) {
             Slog.i(TAG_WM, "Desired input method target: " + imFocus);
             Slog.i(TAG_WM, "Current focus: " + mCurrentFocus + " displayId=" + mDisplayId);
-            Slog.i(TAG_WM, "Last focus: " + mLastFocus + " displayId=" + mDisplayId);
         }
 
         if (DEBUG_INPUT_METHOD) {
@@ -5878,7 +5855,9 @@
                         return false; /* continue */
                     }
                 }
-
+                if (nextWindow.isSecureLocked()) {
+                    return false; /* continue */
+                }
                 return true; /* stop, match found */
             }
         });
@@ -6048,7 +6027,7 @@
 
     class RemoteInsetsControlTarget implements InsetsControlTarget {
         private final IDisplayWindowInsetsController mRemoteInsetsController;
-        private final InsetsState mRequestedInsetsState = new InsetsState();
+        private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
 
         RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) {
             mRemoteInsetsController = controller;
@@ -6110,15 +6089,11 @@
             if (type == ITYPE_IME) {
                 return getInsetsStateController().getImeSourceProvider().isImeShowing();
             }
-            return mRequestedInsetsState.getSourceOrDefaultVisibility(type);
+            return mRequestedVisibilities.getVisibility(type);
         }
 
-        void updateRequestedVisibility(InsetsState state) {
-            for (int i = 0; i < InsetsState.SIZE; i++) {
-                final InsetsSource source = state.peekSource(i);
-                if (source == null) continue;
-                mRequestedInsetsState.addSource(source);
-            }
+        void setRequestedVisibilities(InsetsVisibilities requestedVisibilities) {
+            mRequestedVisibilities.set(requestedVisibilities);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 608be86..686472f 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -140,6 +140,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
@@ -339,7 +340,7 @@
     private int mLastDisableFlags;
     private int mLastAppearance;
     private int mLastBehavior;
-    private final InsetsState mRequestedState = new InsetsState();
+    private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
     private AppearanceRegion[] mLastStatusBarAppearanceRegions;
 
     /** The union of checked bounds while fetching {@link #mStatusBarColorWindows}. */
@@ -2646,6 +2647,7 @@
         final WindowState win = winCandidate;
         mSystemUiControllingWindow = win;
 
+        final int displayId = getDisplayId();
         final int disableFlags = win.getDisableFlags();
         final int opaqueAppearance = updateSystemBarsLw(win, disableFlags);
         final WindowState navColorWin = chooseNavigationColorWindowLw(mNavBarColorWindowCandidate,
@@ -2655,9 +2657,9 @@
         final int appearance = updateLightNavigationBarLw(win.mAttrs.insetsFlags.appearance,
                 navColorWin) | opaqueAppearance;
         final int behavior = win.mAttrs.insetsFlags.behavior;
+        final String focusedApp = win.mAttrs.packageName;
         final boolean isFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR)
                 || !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);
-
         final AppearanceRegion[] appearanceRegions =
                 new AppearanceRegion[mStatusBarColorWindows.size()];
         for (int i = mStatusBarColorWindows.size() - 1; i >= 0; i--) {
@@ -2666,12 +2668,16 @@
                     getStatusBarAppearance(windowState, windowState),
                     new Rect(windowState.getFrame()));
         }
-
-        if (mLastDisableFlags == disableFlags
-                && mLastAppearance == appearance
+        if (mLastDisableFlags != disableFlags) {
+            mLastDisableFlags = disableFlags;
+            final String cause = win.toString();
+            callStatusBarSafely(statusBar -> statusBar.setDisableFlags(displayId, disableFlags,
+                    cause));
+        }
+        if (mLastAppearance == appearance
                 && mLastBehavior == behavior
-                && mRequestedState.equals(win.getRequestedState())
-                && Objects.equals(mFocusedApp, win.mAttrs.packageName)
+                && mRequestedVisibilities.equals(win.getRequestedVisibilities())
+                && Objects.equals(mFocusedApp, focusedApp)
                 && mLastFocusIsFullscreen == isFullscreen
                 && Arrays.equals(mLastStatusBarAppearanceRegions, appearanceRegions)) {
             return false;
@@ -2681,24 +2687,17 @@
             mService.mInputManager.setSystemUiLightsOut(
                     isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
         }
-        mLastDisableFlags = disableFlags;
+        final InsetsVisibilities requestedVisibilities =
+                new InsetsVisibilities(win.getRequestedVisibilities());
         mLastAppearance = appearance;
         mLastBehavior = behavior;
-        mRequestedState.set(win.getRequestedState(), true /* copySources */);
-        mFocusedApp = win.mAttrs.packageName;
+        mRequestedVisibilities = requestedVisibilities;
+        mFocusedApp = focusedApp;
         mLastFocusIsFullscreen = isFullscreen;
         mLastStatusBarAppearanceRegions = appearanceRegions;
-        final String cause = win.toString();
-        mHandler.post(() -> {
-            StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
-            if (statusBar != null) {
-                final int displayId = getDisplayId();
-                statusBar.setDisableFlags(displayId, disableFlags, cause);
-                statusBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                        isNavbarColorManagedByIme, behavior, mRequestedState, mFocusedApp);
-
-            }
-        });
+        callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
+                appearance, appearanceRegions, isNavbarColorManagedByIme, behavior,
+                requestedVisibilities, focusedApp));
         return true;
     }
 
@@ -2710,6 +2709,15 @@
                 : 0;
     }
 
+    private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
+        mHandler.post(() -> {
+            StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+            if (statusBar != null) {
+                consumer.accept(statusBar);
+            }
+        });
+    }
+
     @VisibleForTesting
     @Nullable
     static WindowState chooseNavigationColorWindowLw(WindowState candidate, WindowState imeWindow,
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 6b126ca..0a83784 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -133,12 +133,19 @@
             abortTransient();
         }
         mFocusedWin = focusedWin;
-        InsetsControlTarget statusControlTarget = getStatusControlTarget(focusedWin);
-        InsetsControlTarget navControlTarget = getNavControlTarget(focusedWin);
-        mStateController.onBarControlTargetChanged(statusControlTarget,
-                getFakeControlTarget(focusedWin, statusControlTarget),
+        final InsetsControlTarget statusControlTarget =
+                getStatusControlTarget(focusedWin, false /* fake */);
+        final InsetsControlTarget navControlTarget =
+                getNavControlTarget(focusedWin, false /* fake */);
+        mStateController.onBarControlTargetChanged(
+                statusControlTarget,
+                statusControlTarget == mDummyControlTarget
+                        ? getStatusControlTarget(focusedWin, true /* fake */)
+                        : null,
                 navControlTarget,
-                getFakeControlTarget(focusedWin, navControlTarget));
+                navControlTarget == mDummyControlTarget
+                        ? getNavControlTarget(focusedWin, true /* fake */)
+                        : null);
         mStatusBar.updateVisibility(statusControlTarget, ITYPE_STATUS_BAR);
         mNavBar.updateVisibility(navControlTarget, ITYPE_NAVIGATION_BAR);
     }
@@ -294,13 +301,9 @@
         mShowingTransientTypes.clear();
     }
 
-    private @Nullable InsetsControlTarget getFakeControlTarget(@Nullable WindowState focused,
-            InsetsControlTarget realControlTarget) {
-        return realControlTarget == mDummyControlTarget ? focused : null;
-    }
-
-    private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin) {
-        if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1) {
+    private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin,
+            boolean fake) {
+        if (mShowingTransientTypes.indexOf(ITYPE_STATUS_BAR) != -1 && !fake) {
             return mDummyControlTarget;
         }
         final WindowState notificationShade = mPolicy.getNotificationShade();
@@ -318,7 +321,7 @@
             // we will dispatch the real visibility of status bar to the client.
             return null;
         }
-        if (forceShowsStatusBarTransiently()) {
+        if (forceShowsStatusBarTransiently() && !fake) {
             // Status bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
@@ -343,13 +346,14 @@
                 && !win.inMultiWindowMode();
     }
 
-    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin) {
+    private @Nullable InsetsControlTarget getNavControlTarget(@Nullable WindowState focusedWin,
+            boolean fake) {
         final WindowState imeWin = mDisplayContent.mInputMethodWindow;
         if (imeWin != null && imeWin.isVisible()) {
             // Force showing navigation bar while IME is visible.
             return null;
         }
-        if (mShowingTransientTypes.indexOf(ITYPE_NAVIGATION_BAR) != -1) {
+        if (mShowingTransientTypes.indexOf(ITYPE_NAVIGATION_BAR) != -1 && !fake) {
             return mDummyControlTarget;
         }
         if (focusedWin == mPolicy.getNotificationShade()) {
@@ -366,7 +370,7 @@
             // bar, and we will dispatch the real visibility of navigation bar to the client.
             return null;
         }
-        if (forceShowsNavigationBarTransiently()) {
+        if (forceShowsNavigationBarTransiently() && !fake) {
             // Navigation bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 672ebf1..9cd8c2d 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -49,6 +49,7 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
+import android.view.WindowManager;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -158,6 +159,7 @@
         final boolean keyguardChanged = (keyguardShowing != mKeyguardShowing)
                 || (mKeyguardGoingAway && keyguardShowing && !aodChanged);
         if (!keyguardChanged && !aodChanged) {
+            setWakeTransitionReady();
             return;
         }
         EventLogTags.writeWmSetKeyguardShown(
@@ -203,6 +205,15 @@
         updateKeyguardSleepToken();
         mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
         InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */);
+        setWakeTransitionReady();
+    }
+
+    private void setWakeTransitionReady() {
+        if (mWindowManager.mAtmService.getTransitionController().getCollectingTransitionType()
+                == WindowManager.TRANSIT_WAKE) {
+            mWindowManager.mAtmService.getTransitionController().setReady(
+                    mRootWindowContainer.getDefaultDisplay());
+        }
     }
 
     /**
@@ -334,6 +345,7 @@
         for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
              displayNdx >= 0; displayNdx--) {
             final DisplayContent display = mRootWindowContainer.getChildAt(displayNdx);
+            if (display.isRemoving() || display.isRemoved()) continue;
             final KeyguardDisplayState state = getDisplayState(display.mDisplayId);
             state.updateVisibility(this, display);
             requestDismissKeyguard |= state.mRequestDismissKeyguard;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 455f568..ede4c2e 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1108,13 +1108,15 @@
         }
 
         if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: trimming tasks for " + task);
-        removeForAddTask(task);
+        final int removedIndex = removeForAddTask(task);
 
         task.inRecents = true;
         if (!isAffiliated || needAffiliationFix) {
             // If this is a simple non-affiliated task, or we had some failure trying to
             // handle it as part of an affilated task, then just place it at the top.
-            mTasks.add(0, task);
+            // But if the list is frozen, adding the task to the removed index to keep the order.
+            int indexToAdd = mFreezeTaskListReordering && removedIndex != -1 ? removedIndex : 0;
+            mTasks.add(indexToAdd, task);
             notifyTaskAdded(task);
             if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: adding " + task);
         } else if (isAffiliated) {
@@ -1482,14 +1484,14 @@
      * If needed, remove oldest existing entries in recents that are for the same kind
      * of task as the given one.
      */
-    private void removeForAddTask(Task task) {
+    private int removeForAddTask(Task task) {
         // The adding task will be in recents so it is not hidden.
         mHiddenTasks.remove(task);
 
         final int removeIndex = findRemoveIndexForAddTask(task);
         if (removeIndex == -1) {
             // Nothing to trim
-            return;
+            return removeIndex;
         }
 
         // There is a similar task that will be removed for the addition of {@param task}, but it
@@ -1511,6 +1513,7 @@
             }
         }
         notifyTaskPersisterLocked(removedTask, false /* flush */);
+        return removeIndex;
     }
 
     /**
@@ -1518,11 +1521,6 @@
      * list (if any).
      */
     private int findRemoveIndexForAddTask(Task task) {
-        if (mFreezeTaskListReordering) {
-            // Defer removing tasks due to the addition of new tasks until the task list is unfrozen
-            return -1;
-        }
-
         final int recentsCount = mTasks.size();
         final Intent intent = task.intent;
         final boolean document = intent != null && intent.isDocument();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 629d20e..24c5c82 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2157,7 +2157,7 @@
                     rootTask.setLastRecentsAnimationTransaction(
                             task.mLastRecentsAnimationTransaction,
                             task.mLastRecentsAnimationOverlay);
-                    task.clearLastRecentsAnimationTransaction();
+                    task.clearLastRecentsAnimationTransaction(false /* forceRemoveOverlay */);
                 }
 
                 // There are multiple activities in the task and moving the top activity should
@@ -2212,16 +2212,17 @@
         ensureActivitiesVisible(null, 0, false /* preserveWindows */);
         resumeFocusedTasksTopActivities();
 
-        notifyActivityPipModeChanged(r);
+        notifyActivityPipModeChanged(r.getTask(), r);
     }
 
     /**
      * Notifies when an activity enters or leaves PIP mode.
      *
+     * @param task the task of {@param r}
      * @param r indicates the activity currently in PIP, can be null to indicate no activity is
      *          currently in PIP mode.
      */
-    void notifyActivityPipModeChanged(@Nullable ActivityRecord r) {
+    void notifyActivityPipModeChanged(@NonNull Task task, @Nullable ActivityRecord r) {
         final boolean inPip = r != null;
         if (inPip) {
             mService.getTaskChangeNotificationController().notifyActivityPinned(r);
@@ -2229,6 +2230,9 @@
             mService.getTaskChangeNotificationController().notifyActivityUnpinned();
         }
         mWindowManager.mPolicy.setPipVisibilityLw(inPip);
+        mWmService.mTransactionFactory.get()
+                .setTrustedOverlay(task.getSurfaceControl(), inPip)
+                .apply();
     }
 
     void executeAppTransitionForAllDisplay() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index e282012..0b56777 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -69,6 +69,7 @@
 import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.WindowManager;
@@ -113,7 +114,7 @@
     private float mLastReportedAnimatorScale;
     private String mPackageName;
     private String mRelayoutTag;
-    private final InsetsState mDummyRequestedVisibility = new InsetsState();
+    private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
     private final InsetsSourceControl[] mDummyControls =  new InsetsSourceControl[0];
 
     public Session(WindowManagerService service, IWindowSessionCallback callback) {
@@ -187,29 +188,28 @@
 
     @Override
     public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
-            int viewVisibility, int displayId, InsetsState requestedVisibility,
+            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         return mService.addWindow(this, window, attrs, viewVisibility, displayId,
-                UserHandle.getUserId(mUid), requestedVisibility, outInputChannel, outInsetsState,
+                UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
                 outActiveControls);
     }
 
-
     @Override
     public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
-            int viewVisibility, int displayId, int userId, InsetsState requestedVisibility,
+            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
-                requestedVisibility, outInputChannel, outInsetsState, outActiveControls);
+                requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
     }
 
     @Override
     public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
             int viewVisibility, int displayId, InsetsState outInsetsState) {
         return mService.addWindow(this, window, attrs, viewVisibility, displayId,
-                UserHandle.getUserId(mUid), mDummyRequestedVisibility, null /* outInputChannel */,
+                UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */,
                 outInsetsState, mDummyControls);
     }
 
@@ -624,12 +624,12 @@
     }
 
     @Override
-    public void insetsModified(IWindow window, InsetsState state) {
+    public void updateRequestedVisibilities(IWindow window, InsetsVisibilities visibilities) {
         synchronized (mService.mGlobalLock) {
             final WindowState windowState = mService.windowForClientLocked(this, window,
                     false /* throwOnError */);
             if (windowState != null) {
-                windowState.updateRequestedVisibility(state);
+                windowState.setRequestedVisibilities(visibilities);
                 windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(windowState);
             }
         }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c962e06..2355dde 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -180,7 +180,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -1203,7 +1202,7 @@
                     && (newParent == null || !newParent.inPinnedWindowingMode())) {
                 // Notify if a task from the root pinned task is being removed
                 // (or moved depending on the mode).
-                mRootWindowContainer.notifyActivityPipModeChanged(null);
+                mRootWindowContainer.notifyActivityPipModeChanged(this, null);
             }
         }
 
@@ -3031,18 +3030,6 @@
         return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS);
     }
 
-    @Override
-    RemoteAnimationTarget createRemoteAnimationTarget(
-            RemoteAnimationController.RemoteAnimationRecord record) {
-        final ActivityRecord activity = getTopMostActivity();
-        return activity != null ? activity.createRemoteAnimationTarget(record) : null;
-    }
-
-    @Override
-    boolean canCreateRemoteAnimationTarget() {
-        return true;
-    }
-
     WindowState getTopVisibleAppMainWindow() {
         final ActivityRecord activity = getTopVisibleActivity();
         return activity != null ? activity.findMainWindow() : null;
@@ -3527,7 +3514,9 @@
     StartingWindowInfo getStartingWindowInfo(ActivityRecord activity) {
         final StartingWindowInfo info = new StartingWindowInfo();
         info.taskInfo = getTaskInfo();
-
+        info.targetActivityInfo = info.taskInfo.topActivityInfo != null
+                && activity.info != info.taskInfo.topActivityInfo
+                ? activity.info : null;
         info.isKeyguardOccluded =
             mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY);
 
@@ -4327,10 +4316,10 @@
      * @return true if the task is currently focused.
      */
     private boolean isFocused() {
-        if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) {
+        if (mDisplayContent == null || mDisplayContent.mFocusedApp == null) {
             return false;
         }
-        return mDisplayContent.mCurrentFocus.getTask() == this;
+        return mDisplayContent.mFocusedApp.getTask() == this;
     }
 
     /**
@@ -4387,10 +4376,9 @@
      * Called on the task of a window which gained or lost focus.
      * @param hasFocus
      */
-    void onWindowFocusChanged(boolean hasFocus) {
+    void onAppFocusChanged(boolean hasFocus) {
         updateShadowsRadius(hasFocus, getSyncTransaction());
-        // TODO(b/180525887): Un-comment once there is resolution on the bug.
-        // dispatchTaskInfoChangedIfNeeded(false /* force */);
+        dispatchTaskInfoChangedIfNeeded(false /* force */);
     }
 
     void onPictureInPictureParamsChanged() {
@@ -4576,7 +4564,7 @@
                     : WINDOWING_MODE_FULLSCREEN;
         }
         if (currentMode == WINDOWING_MODE_PINNED) {
-            mRootWindowContainer.notifyActivityPipModeChanged(null);
+            mRootWindowContainer.notifyActivityPipModeChanged(this, null);
         }
         if (likelyResolvedMode == WINDOWING_MODE_PINNED) {
             // In the case that we've disabled affecting the SysUI flags as a part of seamlessly
@@ -6043,7 +6031,10 @@
         mLastRecentsAnimationOverlay = overlay;
     }
 
-    void clearLastRecentsAnimationTransaction() {
+    void clearLastRecentsAnimationTransaction(boolean forceRemoveOverlay) {
+        if (forceRemoveOverlay && mLastRecentsAnimationOverlay != null) {
+            getPendingTransaction().remove(mLastRecentsAnimationOverlay);
+        }
         mLastRecentsAnimationTransaction = null;
         mLastRecentsAnimationOverlay = null;
         // reset also the crop and transform introduced by mLastRecentsAnimationTransaction
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 10c16ff..6c8cde43 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -780,10 +780,12 @@
             }
             return SCREEN_ORIENTATION_UNSPECIFIED;
         } else {
-            // Apps and their containers are not allowed to specify an orientation of full screen
-            // tasks created by organizer. The organizer handles the orientation instead.
-            final Task task = getTopRootTaskInWindowingMode(WINDOWING_MODE_FULLSCREEN);
-            if (task != null && task.isVisible() && task.mCreatedByOrganizer) {
+            // Apps and their containers are not allowed to specify an orientation of non floating
+            // visible tasks created by organizer. The organizer handles the orientation instead.
+            final Task nonFloatingTopTask =
+                    getRootTask(t -> !t.getWindowConfiguration().tasksAreFloating());
+            if (nonFloatingTopTask != null && nonFloatingTopTask.mCreatedByOrganizer
+                    && nonFloatingTopTask.isVisible()) {
                 return SCREEN_ORIENTATION_UNSPECIFIED;
             }
         }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index ba13546..1eec6aa 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -83,9 +83,11 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
+import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
 import android.window.ITaskFragmentOrganizer;
 import android.window.TaskFragmentInfo;
+import android.window.TaskFragmentOrganizerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -285,8 +287,8 @@
         taskFragment.mAdjacentTaskFragment = this;
     }
 
-    void setTaskFragmentOrganizer(ITaskFragmentOrganizer organizer, int pid) {
-        mTaskFragmentOrganizer = organizer;
+    void setTaskFragmentOrganizer(TaskFragmentOrganizerToken organizer, int pid) {
+        mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
         mTaskFragmentOrganizerPid = pid;
     }
 
@@ -1486,6 +1488,18 @@
         // No app transition applied to the task fragment.
     }
 
+    @Override
+    RemoteAnimationTarget createRemoteAnimationTarget(
+            RemoteAnimationController.RemoteAnimationRecord record) {
+        final ActivityRecord activity = getTopMostActivity();
+        return activity != null ? activity.createRemoteAnimationTarget(record) : null;
+    }
+
+    @Override
+    boolean canCreateRemoteAnimationTarget() {
+        return true;
+    }
+
     boolean shouldSleepActivities() {
         return false;
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 4843e5a..a322384 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -116,8 +116,11 @@
         }
 
         void dispose() {
-            mOrganizedTaskFragments.forEach(TaskFragment::removeImmediately);
-            mOrganizedTaskFragments.clear();
+            while (!mOrganizedTaskFragments.isEmpty()) {
+                final TaskFragment taskFragment = mOrganizedTaskFragments.get(0);
+                taskFragment.removeImmediately();
+                mOrganizedTaskFragments.remove(taskFragment);
+            }
             mOrganizer.asBinder().unlinkToDeath(this, 0 /*flags*/);
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index cc4abab..059eb87 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -72,6 +72,7 @@
 import android.view.IWindowSession;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
@@ -166,6 +167,7 @@
         final ClientWindowFrames tmpFrames = new ClientWindowFrames();
         final Rect taskBounds;
         final InsetsState mTmpInsetsState = new InsetsState();
+        final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
         final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0];
         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
         final TaskDescription taskDescription = new TaskDescription();
@@ -227,7 +229,8 @@
         int displayId = activity.getDisplayContent().getDisplayId();
         try {
             final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
-                    mTmpInsetsState, null /* outInputChannel */, mTmpInsetsState, mTempControls);
+                    mRequestedVisibilities, null /* outInputChannel */, mTmpInsetsState,
+                    mTempControls);
             if (res < 0) {
                 Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
                 return null;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2c17b73..43165f8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -33,6 +33,8 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.TransitionFlags;
+import static android.view.WindowManager.TransitionType;
 import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -59,7 +61,6 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.view.SurfaceControl;
-import android.view.WindowManager;
 import android.view.animation.Animation;
 import android.window.IRemoteTransition;
 import android.window.TransitionInfo;
@@ -109,9 +110,9 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionState {}
 
-    final @WindowManager.TransitionType int mType;
+    final @TransitionType int mType;
     private int mSyncId;
-    private @WindowManager.TransitionFlags int mFlags;
+    private @TransitionFlags int mFlags;
     private final TransitionController mController;
     private final BLASTSyncEngine mSyncEngine;
     private IRemoteTransition mRemoteTransition = null;
@@ -146,7 +147,7 @@
     private boolean mNavBarAttachedToApp = false;
     private int mNavBarDisplayId = INVALID_DISPLAY;
 
-    Transition(@WindowManager.TransitionType int type, @WindowManager.TransitionFlags int flags,
+    Transition(@TransitionType int type, @TransitionFlags int flags,
             TransitionController controller, BLASTSyncEngine syncEngine) {
         mType = type;
         mFlags = flags;
@@ -617,7 +618,7 @@
     }
 
     private void handleNonAppWindowsInTransition(int displayId,
-            @WindowManager.TransitionType int transit, int flags) {
+            @TransitionType int transit, int flags) {
         final DisplayContent dc =
                 mController.mAtm.mRootWindowContainer.getDisplayContent(displayId);
         if (dc == null) {
@@ -968,7 +969,7 @@
      */
     @VisibleForTesting
     @NonNull
-    static TransitionInfo calculateTransitionInfo(int type, int flags,
+    static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
             ArraySet<WindowContainer> targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
         final TransitionInfo out = new TransitionInfo(type, flags);
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a4db2dc..7f6dce4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -249,6 +249,7 @@
 import android.view.InputWindowHandle;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
@@ -451,14 +452,14 @@
     /**
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
      */
-    public static final boolean sEnableRemoteKeyguardGoingAwayAnimation = !sEnableShellTransitions
-            && sEnableRemoteKeyguardAnimation >= 1;
+    public static final boolean sEnableRemoteKeyguardGoingAwayAnimation =
+            sEnableRemoteKeyguardAnimation >= 1;
 
     /**
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
      */
-    public static final boolean sEnableRemoteKeyguardOccludeAnimation = !sEnableShellTransitions
-            && sEnableRemoteKeyguardAnimation >= 2;
+    public static final boolean sEnableRemoteKeyguardOccludeAnimation =
+            sEnableRemoteKeyguardAnimation >= 2;
 
     /**
      * Allows a fullscreen windowing mode activity to launch in its desired orientation directly
@@ -1456,7 +1457,7 @@
     }
 
     public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
-            int displayId, int requestUserId, InsetsState requestedVisibility,
+            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
             InputChannel outInputChannel, InsetsState outInsetsState,
             InsetsSourceControl[] outActiveControls) {
         Arrays.fill(outActiveControls, null);
@@ -1678,7 +1679,7 @@
 
             final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
             displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
-            win.updateRequestedVisibility(requestedVisibility);
+            win.setRequestedVisibilities(requestedVisibilities);
 
             res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
             if (res != ADD_OKAY) {
@@ -4158,7 +4159,7 @@
     }
 
     @Override
-    public void modifyDisplayWindowInsets(int displayId, InsetsState state) {
+    public void updateDisplayWindowRequestedVisibilities(int displayId, InsetsVisibilities vis) {
         if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
@@ -4170,7 +4171,7 @@
                 if (dc == null || dc.mRemoteInsetsControlTarget == null) {
                     return;
                 }
-                dc.mRemoteInsetsControlTarget.updateRequestedVisibility(state);
+                dc.mRemoteInsetsControlTarget.setRequestedVisibilities(vis);
                 dc.getInsetsStateController().onInsetsModified(dc.mRemoteInsetsControlTarget);
             }
         } finally {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 0be21cb..abf8afa 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1109,11 +1109,13 @@
             @Nullable IBinder errorCallbackToken) {
         final ActivityRecord ownerActivity =
                 ActivityRecord.forTokenLocked(creationParams.getOwnerToken());
+        final ITaskFragmentOrganizer organizer = ITaskFragmentOrganizer.Stub.asInterface(
+                creationParams.getOrganizer().asBinder());
+
         if (ownerActivity == null || ownerActivity.getTask() == null) {
             final Throwable exception =
                     new IllegalArgumentException("Not allowed to operate with invalid ownerToken");
-            sendTaskFragmentOperationFailure(creationParams.getOrganizer(), errorCallbackToken,
-                    exception);
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return;
         }
         // The ownerActivity has to belong to the same app as the root Activity of the target Task.
@@ -1122,8 +1124,7 @@
             final Throwable exception =
                     new IllegalArgumentException("Not allowed to operate with the ownerToken while "
                             + "the root activity of the target task belong to the different app");
-            sendTaskFragmentOperationFailure(creationParams.getOrganizer(), errorCallbackToken,
-                    exception);
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
             return;
         }
         final TaskFragment taskFragment = new TaskFragment(mService,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7e721be..c9b1506 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -238,6 +238,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
@@ -748,7 +749,7 @@
     private boolean mIsDimming = false;
 
     private @Nullable InsetsSourceProvider mControllableInsetProvider;
-    private final InsetsState mRequestedInsetsState = new InsetsState();
+    private final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
 
     /**
      * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client.
@@ -871,27 +872,23 @@
      */
     @Override
     public boolean getRequestedVisibility(@InternalInsetsType int type) {
-        return mRequestedInsetsState.getSourceOrDefaultVisibility(type);
+        return mRequestedVisibilities.getVisibility(type);
     }
 
     /**
      * Returns all the requested visibilities.
      *
-     * @return an {@link InsetsState} as the requested visibilities.
+     * @return an {@link InsetsVisibilities} as the requested visibilities.
      */
-    InsetsState getRequestedState() {
-        return mRequestedInsetsState;
+    InsetsVisibilities getRequestedVisibilities() {
+        return mRequestedVisibilities;
     }
 
     /**
      * @see #getRequestedVisibility(int)
      */
-    void updateRequestedVisibility(InsetsState state) {
-        for (int i = 0; i < InsetsState.SIZE; i++) {
-            final InsetsSource source = state.peekSource(i);
-            if (source == null) continue;
-            mRequestedInsetsState.addSource(source);
-        }
+    void setRequestedVisibilities(InsetsVisibilities visibilities) {
+        mRequestedVisibilities.set(visibilities);
     }
 
     /**
@@ -1172,7 +1169,8 @@
         if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) {
             return TouchOcclusionMode.USE_OPACITY;
         }
-        if (isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_ALL)) {
+        if (isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_ALL)
+                || mWmService.mAtmService.getTransitionController().inTransition(this)) {
             return TouchOcclusionMode.USE_OPACITY;
         }
         return TouchOcclusionMode.BLOCK_UNTRUSTED;
@@ -4404,9 +4402,9 @@
             pw.println(prefix + "mEmbeddedDisplayContents=" + mEmbeddedDisplayContents);
         }
         if (dumpAll) {
-            final String visibilityString = mRequestedInsetsState.toSourceVisibilityString();
+            final String visibilityString = mRequestedVisibilities.toString();
             if (!visibilityString.isEmpty()) {
-                pw.println(prefix + "Requested visibility: " + visibilityString);
+                pw.println(prefix + "Requested visibilities: " + visibilityString);
             }
         }
     }
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index e3e2708..fdf23d3 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -59,9 +59,7 @@
 
     private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG);
 
-    private static final long BG_PROCESS_PERIOD = DEBUG
-            ? TimeUnit.MINUTES.toMillis(1)
-            : TimeUnit.DAYS.toMillis(1);
+    private static final long BG_PROCESS_PERIOD = TimeUnit.DAYS.toMillis(1); // every 1 day.
 
     private IProfCollectd mIProfcollect;
     private static ProfcollectForwardingService sSelfService;
@@ -286,6 +284,11 @@
         updateEngine.bind(new UpdateEngineCallback() {
             @Override
             public void onStatusUpdate(int status, float percent) {
+                if (DEBUG) {
+                    Log.d(LOG_TAG, "Received OTA status update, status: " + status + ", percent: "
+                            + percent);
+                }
+
                 if (status == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) {
                     packProfileReport();
                 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index a254f68..16afef5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -3002,7 +3002,8 @@
             final PendingIntent pi = getNewMockPendingIntent();
             setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i, pi);
 
-            verify(() -> MetricsHelper.pushAlarmScheduled(argThat(a -> a.matches(pi, null))));
+            verify(() -> MetricsHelper.pushAlarmScheduled(argThat(a -> a.matches(pi, null)),
+                    anyInt()));
         }
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 803a0c1..26b5218 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -1003,7 +1003,7 @@
                     dummyPackageName, dummyClassName), "", definingUid));
         }
         app.mServices.setConnectionGroup(connectionGroup);
-        app.mState.setSetProcState(procState);
+        app.mState.setReportedProcState(procState);
         app.mProfile.setLastMemInfo(spy(new Debug.MemoryInfo()));
         app.mProfile.setLastPss(pss);
         app.mProfile.setLastRss(rss);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
index 8336663..9926953 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
@@ -50,6 +50,14 @@
                 AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
         assertThat(appSearchConfig.getCachedSamplingIntervalForPutDocumentStats()).isEqualTo(
                 AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForInitializeStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForSearchStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForGlobalSearchStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForOptimizeStats()).isEqualTo(
+                AppSearchConfig.DEFAULT_SAMPLING_INTERVAL);
         assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentSizeBytes()).isEqualTo(
                 AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
         assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(
@@ -100,6 +108,11 @@
         final int samplingIntervalDefault = -1;
         final int samplingIntervalPutDocumentStats = -2;
         final int samplingIntervalBatchCallStats = -3;
+        final int samplingIntervalInitializeStats = -4;
+        final int samplingIntervalSearchStats = -5;
+        final int samplingIntervalGlobalSearchStats = -6;
+        final int samplingIntervalOptimizeStats = -7;
+
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
                 AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
                 Integer.toString(samplingIntervalDefault),
@@ -112,6 +125,22 @@
                 AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
                 Integer.toString(samplingIntervalBatchCallStats),
                 false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
+                Integer.toString(samplingIntervalInitializeStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
+                Integer.toString(samplingIntervalSearchStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
+                Integer.toString(samplingIntervalGlobalSearchStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
+                Integer.toString(samplingIntervalOptimizeStats),
+                false);
 
         AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
 
@@ -121,6 +150,14 @@
                 samplingIntervalPutDocumentStats);
         assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
                 samplingIntervalBatchCallStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForInitializeStats()).isEqualTo(
+                samplingIntervalInitializeStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForSearchStats()).isEqualTo(
+                samplingIntervalSearchStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForGlobalSearchStats()).isEqualTo(
+                samplingIntervalGlobalSearchStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForOptimizeStats()).isEqualTo(
+                samplingIntervalOptimizeStats);
     }
 
     @Test
@@ -128,6 +165,10 @@
         int samplingIntervalDefault = -1;
         int samplingIntervalPutDocumentStats = -2;
         int samplingIntervalBatchCallStats = -3;
+        int samplingIntervalInitializeStats = -4;
+        int samplingIntervalSearchStats = -5;
+        int samplingIntervalGlobalSearchStats = -6;
+        int samplingIntervalOptimizeStats = -7;
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
                 AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
                 Integer.toString(samplingIntervalDefault),
@@ -140,12 +181,32 @@
                 AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
                 Integer.toString(samplingIntervalBatchCallStats),
                 false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
+                Integer.toString(samplingIntervalInitializeStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
+                Integer.toString(samplingIntervalSearchStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
+                Integer.toString(samplingIntervalGlobalSearchStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
+                Integer.toString(samplingIntervalOptimizeStats),
+                false);
         AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
 
         // Overrides
         samplingIntervalDefault = -4;
         samplingIntervalPutDocumentStats = -5;
         samplingIntervalBatchCallStats = -6;
+        samplingIntervalInitializeStats = -7;
+        samplingIntervalSearchStats = -8;
+        samplingIntervalGlobalSearchStats = -9;
+        samplingIntervalOptimizeStats = -10;
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
                 AppSearchConfig.KEY_SAMPLING_INTERVAL_DEFAULT,
                 Integer.toString(samplingIntervalDefault),
@@ -158,6 +219,22 @@
                 AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_BATCH_CALL_STATS,
                 Integer.toString(samplingIntervalBatchCallStats),
                 false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_INITIALIZE_STATS,
+                Integer.toString(samplingIntervalInitializeStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_SEARCH_STATS,
+                Integer.toString(samplingIntervalSearchStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_GLOBAL_SEARCH_STATS,
+                Integer.toString(samplingIntervalGlobalSearchStats),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_SAMPLING_INTERVAL_FOR_OPTIMIZE_STATS,
+                Integer.toString(samplingIntervalOptimizeStats),
+                false);
 
         assertThat(appSearchConfig.getCachedSamplingIntervalDefault()).isEqualTo(
                 samplingIntervalDefault);
@@ -165,6 +242,14 @@
                 samplingIntervalPutDocumentStats);
         assertThat(appSearchConfig.getCachedSamplingIntervalForBatchCallStats()).isEqualTo(
                 samplingIntervalBatchCallStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForInitializeStats()).isEqualTo(
+                samplingIntervalInitializeStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForSearchStats()).isEqualTo(
+                samplingIntervalSearchStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForGlobalSearchStats()).isEqualTo(
+                samplingIntervalGlobalSearchStats);
+        assertThat(appSearchConfig.getCachedSamplingIntervalForOptimizeStats()).isEqualTo(
+                samplingIntervalOptimizeStats);
     }
 
     /**
@@ -366,6 +451,18 @@
                 () -> appSearchConfig.getCachedSamplingIntervalForPutDocumentStats());
         Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
                 IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForInitializeStats());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForSearchStats());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForGlobalSearchStats());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedSamplingIntervalForOptimizeStats());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
                 () -> appSearchConfig.getCachedBytesOptimizeThreshold());
         Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
                 IllegalStateException.class,
diff --git a/services/tests/mockingservicestests/src/com/android/server/appsearch/OWNERS b/services/tests/mockingservicestests/src/com/android/server/appsearch/OWNERS
new file mode 100644
index 0000000..24f6b0b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/appsearch/OWNERS
@@ -0,0 +1 @@
+include /apex/appsearch/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index 5b067bc..f40a5ad 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -42,6 +42,7 @@
 
 import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
 import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
+import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
 import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
 import com.android.server.appsearch.icing.proto.DocumentProto;
 import com.android.server.appsearch.icing.proto.GetOptimizeInfoResultProto;
@@ -451,19 +452,26 @@
         assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1);
 
         // Increase mutation counter and stop before reach the threshold
-        mAppSearchImpl.checkForOptimize(AppSearchImpl.CHECK_OPTIMIZE_INTERVAL - 1);
+        mAppSearchImpl.checkForOptimize(
+                AppSearchImpl.CHECK_OPTIMIZE_INTERVAL - 1, /*builder=*/ null);
 
         // Verify the optimize() isn't triggered.
         optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1);
 
         // Increase the counter and reach the threshold, optimize() should be triggered.
-        mAppSearchImpl.checkForOptimize(/*mutateBatchSize=*/ 1);
+        OptimizeStats.Builder builder = new OptimizeStats.Builder();
+        mAppSearchImpl.checkForOptimize(/*mutateBatchSize=*/ 1, builder);
 
         // Verify optimize() is triggered.
         optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked();
         assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0);
         assertThat(optimizeInfo.getEstimatedOptimizableBytes()).isEqualTo(0);
+
+        // Verify the stats have been set.
+        OptimizeStats oStats = builder.build();
+        assertThat(oStats.getOriginalDocumentCount()).isEqualTo(1);
+        assertThat(oStats.getDeletedDocumentCount()).isEqualTo(1);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
index 7bacbb6..7c97687 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java
@@ -29,12 +29,14 @@
 
 import com.android.server.appsearch.external.localstorage.stats.CallStats;
 import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
+import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
 import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
 import com.android.server.appsearch.external.localstorage.stats.RemoveStats;
 import com.android.server.appsearch.external.localstorage.stats.SearchStats;
 import com.android.server.appsearch.icing.proto.DeleteStatsProto;
 import com.android.server.appsearch.icing.proto.DocumentProto;
 import com.android.server.appsearch.icing.proto.InitializeStatsProto;
+import com.android.server.appsearch.icing.proto.OptimizeStatsProto;
 import com.android.server.appsearch.icing.proto.PutDocumentStatsProto;
 import com.android.server.appsearch.icing.proto.PutResultProto;
 import com.android.server.appsearch.icing.proto.QueryStatsProto;
@@ -81,6 +83,7 @@
         @Nullable InitializeStats mInitializeStats;
         @Nullable SearchStats mSearchStats;
         @Nullable RemoveStats mRemoveStats;
+        @Nullable OptimizeStats mOptimizeStats;
 
         @Override
         public void logStats(@NonNull CallStats stats) {
@@ -106,6 +109,11 @@
         public void logStats(@NonNull RemoveStats stats) {
             mRemoveStats = stats;
         }
+
+        @Override
+        public void logStats(@NonNull OptimizeStats stats) {
+            mOptimizeStats = stats;
+        }
     }
 
     @Test
@@ -286,6 +294,48 @@
         assertThat(rStats.getDeletedDocumentCount()).isEqualTo(nativeNumDocumentDeleted);
     }
 
+    @Test
+    public void testAppSearchLoggerHelper_testCopyNativeStats_optimize() {
+        int nativeLatencyMillis = 1;
+        int nativeDocumentStoreOptimizeLatencyMillis = 2;
+        int nativeIndexRestorationLatencyMillis = 3;
+        int nativeNumOriginalDocuments = 4;
+        int nativeNumDeletedDocuments = 5;
+        int nativeNumExpiredDocuments = 6;
+        long nativeStorageSizeBeforeBytes = Integer.MAX_VALUE + 1;
+        long nativeStorageSizeAfterBytes = Integer.MAX_VALUE + 2;
+        long nativeTimeSinceLastOptimizeMillis = Integer.MAX_VALUE + 3;
+        OptimizeStatsProto optimizeStatsProto =
+                OptimizeStatsProto.newBuilder()
+                        .setLatencyMs(nativeLatencyMillis)
+                        .setDocumentStoreOptimizeLatencyMs(nativeDocumentStoreOptimizeLatencyMillis)
+                        .setIndexRestorationLatencyMs(nativeIndexRestorationLatencyMillis)
+                        .setNumOriginalDocuments(nativeNumOriginalDocuments)
+                        .setNumDeletedDocuments(nativeNumDeletedDocuments)
+                        .setNumExpiredDocuments(nativeNumExpiredDocuments)
+                        .setStorageSizeBefore(nativeStorageSizeBeforeBytes)
+                        .setStorageSizeAfter(nativeStorageSizeAfterBytes)
+                        .setTimeSinceLastOptimizeMs(nativeTimeSinceLastOptimizeMillis)
+                        .build();
+        OptimizeStats.Builder oBuilder = new OptimizeStats.Builder();
+
+        AppSearchLoggerHelper.copyNativeStats(optimizeStatsProto, oBuilder);
+
+        OptimizeStats oStats = oBuilder.build();
+        assertThat(oStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis);
+        assertThat(oStats.getDocumentStoreOptimizeLatencyMillis())
+                .isEqualTo(nativeDocumentStoreOptimizeLatencyMillis);
+        assertThat(oStats.getIndexRestorationLatencyMillis())
+                .isEqualTo(nativeIndexRestorationLatencyMillis);
+        assertThat(oStats.getOriginalDocumentCount()).isEqualTo(nativeNumOriginalDocuments);
+        assertThat(oStats.getDeletedDocumentCount()).isEqualTo(nativeNumDeletedDocuments);
+        assertThat(oStats.getExpiredDocumentCount()).isEqualTo(nativeNumExpiredDocuments);
+        assertThat(oStats.getStorageSizeBeforeBytes()).isEqualTo(nativeStorageSizeBeforeBytes);
+        assertThat(oStats.getStorageSizeAfterBytes()).isEqualTo(nativeStorageSizeAfterBytes);
+        assertThat(oStats.getTimeSinceLastOptimizeMillis())
+                .isEqualTo(nativeTimeSinceLastOptimizeMillis);
+    }
+
     //
     // Testing actual logging
     //
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
index 57d9941..c1dc0e4 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/stats/AppSearchStatsTest.java
@@ -348,4 +348,49 @@
         assertThat(rStats.getDeleteType()).isEqualTo(deleteType);
         assertThat(rStats.getDeletedDocumentCount()).isEqualTo(documentDeletedCount);
     }
+
+    @Test
+    public void testAppSearchStats_OptimizeStats() {
+        int nativeLatencyMillis = 1;
+        int nativeDocumentStoreOptimizeLatencyMillis = 2;
+        int nativeIndexRestorationLatencyMillis = 3;
+        int nativeNumOriginalDocuments = 4;
+        int nativeNumDeletedDocuments = 5;
+        int nativeNumExpiredDocuments = 6;
+        long nativeStorageSizeBeforeBytes = Integer.MAX_VALUE + 1;
+        long nativeStorageSizeAfterBytes = Integer.MAX_VALUE + 2;
+        long nativeTimeSinceLastOptimizeMillis = Integer.MAX_VALUE + 3;
+
+        final OptimizeStats oStats =
+                new OptimizeStats.Builder()
+                        .setStatusCode(TEST_STATUS_CODE)
+                        .setTotalLatencyMillis(TEST_TOTAL_LATENCY_MILLIS)
+                        .setNativeLatencyMillis(nativeLatencyMillis)
+                        .setDocumentStoreOptimizeLatencyMillis(
+                                nativeDocumentStoreOptimizeLatencyMillis)
+                        .setIndexRestorationLatencyMillis(nativeIndexRestorationLatencyMillis)
+                        .setOriginalDocumentCount(nativeNumOriginalDocuments)
+                        .setDeletedDocumentCount(nativeNumDeletedDocuments)
+                        .setExpiredDocumentCount(nativeNumExpiredDocuments)
+                        .setStorageSizeBeforeBytes(nativeStorageSizeBeforeBytes)
+                        .setStorageSizeAfterBytes(nativeStorageSizeAfterBytes)
+                        .setTimeSinceLastOptimizeMillis(nativeTimeSinceLastOptimizeMillis)
+                        .build();
+
+        assertThat(oStats.getStatusCode()).isEqualTo(TEST_STATUS_CODE);
+        assertThat(oStats.getTotalLatencyMillis()).isEqualTo(TEST_TOTAL_LATENCY_MILLIS);
+        assertThat(oStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis);
+        assertThat(oStats.getNativeLatencyMillis()).isEqualTo(nativeLatencyMillis);
+        assertThat(oStats.getDocumentStoreOptimizeLatencyMillis())
+                .isEqualTo(nativeDocumentStoreOptimizeLatencyMillis);
+        assertThat(oStats.getIndexRestorationLatencyMillis())
+                .isEqualTo(nativeIndexRestorationLatencyMillis);
+        assertThat(oStats.getOriginalDocumentCount()).isEqualTo(nativeNumOriginalDocuments);
+        assertThat(oStats.getDeletedDocumentCount()).isEqualTo(nativeNumDeletedDocuments);
+        assertThat(oStats.getExpiredDocumentCount()).isEqualTo(nativeNumExpiredDocuments);
+        assertThat(oStats.getStorageSizeBeforeBytes()).isEqualTo(nativeStorageSizeBeforeBytes);
+        assertThat(oStats.getStorageSizeAfterBytes()).isEqualTo(nativeStorageSizeAfterBytes);
+        assertThat(oStats.getTimeSinceLastOptimizeMillis())
+                .isEqualTo(nativeTimeSinceLastOptimizeMillis);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 1fe4123..8592166a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -360,7 +360,8 @@
                     false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
                     TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
                     0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
-                    false /* isKeyguard */, true /* shouldVibrate */);
+                    false /* isKeyguard */, true /* shouldVibrate */,
+                    false /* isKeyguardBypassEnabled */);
         }
 
         @Override
@@ -388,7 +389,8 @@
                     false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */,
                     TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */,
                     0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class),
-                    false /* isKeyguard */, true /* shouldVibrate */);
+                    false /* isKeyguard */, true /* shouldVibrate */,
+                    false /* isKeyguardBypassEnabled */);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
index fb05825..f1adcae 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java
@@ -17,6 +17,7 @@
 package com.android.server.biometrics.sensors;
 
 import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_FACE;
+import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_FP_OTHER;
 import static com.android.server.biometrics.sensors.BiometricScheduler.SENSOR_TYPE_UDFPS;
 
 import static junit.framework.Assert.assertEquals;
@@ -259,6 +260,117 @@
     }
 
     @Test
+    public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+        when(faceClient.isKeyguard()).thenReturn(true);
+        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+
+        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
+                withSettings().extraInterfaces(Udfps.class));
+        when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(udfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient,
+                LockoutTracker.LOCKOUT_NONE, mCallback);
+        verify(mCallback, never()).sendHapticFeedback();
+        verify(mCallback).handleLifecycleAfterAuth();
+
+        // BiometricScheduler removes the face authentication client after rejection
+        mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+
+        // Then UDFPS rejected
+        CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class);
+        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, udfpsClient,
+                LockoutTracker.LOCKOUT_NONE, udfpsCallback);
+        verify(udfpsCallback).sendHapticFeedback();
+        verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
+        verify(mCallback, never()).sendHapticFeedback();
+    }
+
+    @Test
+    public void testKeyguard_udfpsRejected_thenFaceRejected() {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+        when(faceClient.isKeyguard()).thenReturn(true);
+        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+
+        AuthenticationClient<?> udfpsClient = mock(AuthenticationClient.class,
+                withSettings().extraInterfaces(Udfps.class));
+        when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(udfpsClient.isKeyguard()).thenReturn(true);
+        when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient);
+
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient,
+                LockoutTracker.LOCKOUT_NONE, mCallback);
+        // Client becomes paused, but finger does not necessarily lift, since we suppress the haptic
+        when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED_PAUSED);
+        verify(mCallback, never()).sendHapticFeedback();
+        verify(mCallback).handleLifecycleAfterAuth();
+
+        // Then face rejected. Note that scheduler leaves UDFPS in the CoexCoordinator since
+        // unlike face, its lifecycle becomes "paused" instead of "finished".
+        CoexCoordinator.Callback faceCallback = mock(CoexCoordinator.Callback.class);
+        mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, faceClient,
+                LockoutTracker.LOCKOUT_NONE, faceCallback);
+        verify(faceCallback).sendHapticFeedback();
+        verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
+        verify(mCallback, never()).sendHapticFeedback();
+    }
+
+    @Test
+    public void testKeyguard_capacitiveAccepted_whenFaceScanning() {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+        when(faceClient.isKeyguard()).thenReturn(true);
+        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+
+        AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
+        when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(fpClient.isKeyguard()).thenReturn(true);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
+
+        mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, fpClient, mCallback);
+        verify(mCallback).sendHapticFeedback();
+        verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */);
+        verify(mCallback).handleLifecycleAfterAuth();
+    }
+
+    @Test
+    public void testKeyguard_capacitiveRejected_whenFaceScanning() {
+        mCoexCoordinator.reset();
+
+        AuthenticationClient<?> faceClient = mock(AuthenticationClient.class);
+        when(faceClient.isKeyguard()).thenReturn(true);
+        when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+
+        AuthenticationClient<?> fpClient = mock(AuthenticationClient.class);
+        when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED);
+        when(fpClient.isKeyguard()).thenReturn(true);
+
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient);
+        mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient);
+
+        mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, fpClient,
+                LockoutTracker.LOCKOUT_NONE, mCallback);
+        verify(mCallback).sendHapticFeedback();
+        verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */);
+        verify(mCallback).handleLifecycleAfterAuth();
+    }
+
+    @Test
     public void testNonKeyguard_rejectAndNotLockedOut() {
         mCoexCoordinator.reset();
 
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 1ad8850..cc3591c8 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -92,7 +92,6 @@
 
     @Mock IThermalService mThermalServiceMock;
     @Mock Injector mInjectorMock;
-    @Mock BrightnessSetting mBrightnessSetting;
 
     @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
 
@@ -123,7 +122,7 @@
         initHandler(null);
         final HighBrightnessModeController hbmc = new HighBrightnessModeController(
                 mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
-                DEFAULT_MAX, null, () -> {}, mContextSpy, mBrightnessSetting);
+                DEFAULT_MAX, null, () -> {}, mContextSpy);
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
     }
 
@@ -132,7 +131,7 @@
         initHandler(null);
         final HighBrightnessModeController hbmc = new HighBrightnessModeController(
                 mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
-                DEFAULT_MAX, null, () -> {}, mContextSpy, mBrightnessSetting);
+                DEFAULT_MAX, null, () -> {}, mContextSpy);
         hbmc.setAutoBrightnessEnabled(true);
         hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
@@ -464,7 +463,7 @@
         initHandler(clock);
         return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
                 DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {},
-                mContextSpy, mBrightnessSetting);
+                mContextSpy);
     }
 
     private void initHandler(OffsettableClock clock) {
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 645fa63..9e46e1f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -93,6 +93,7 @@
 import android.view.Display;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -103,7 +104,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -502,6 +502,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testBoundWidgetPackageExempt() throws Exception {
         assumeTrue(mInjector.getContext().getSystemService(AppWidgetManager.class) != null);
         assertEquals(STANDBY_BUCKET_ACTIVE,
@@ -584,6 +585,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testIsAppIdle_Charging() throws Exception {
         TestParoleListener paroleListener = new TestParoleListener();
         mController.addListener(paroleListener);
@@ -616,6 +618,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testIsAppIdle_Enabled() throws Exception {
         setChargingState(mController, false);
         TestParoleListener paroleListener = new TestParoleListener();
@@ -715,6 +718,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testBuckets() throws Exception {
         assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
 
@@ -747,6 +751,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSetAppStandbyBucket() throws Exception {
         // For a known package, standby bucket should be set properly
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
@@ -766,6 +771,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testAppStandbyBucketOnInstallAndUninstall() throws Exception {
         // On package install, standby bucket should be ACTIVE
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_UNKNOWN);
@@ -784,6 +790,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testScreenTimeAndBuckets() throws Exception {
         mInjector.setDisplayOn(false);
 
@@ -807,6 +814,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testForcedIdle() throws Exception {
         mController.forceIdleState(PACKAGE_1, USER_ID, true);
         assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
@@ -819,6 +827,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testNotificationEvent() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
@@ -832,6 +841,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSlicePinnedEvent() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
@@ -845,6 +855,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSlicePinnedPrivEvent() throws Exception {
         mController.forceIdleState(PACKAGE_1, USER_ID, true);
         reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime, PACKAGE_1);
@@ -852,6 +863,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testPredictionTimedOut() throws Exception {
         // Set it to timeout or usage, so that prediction can override it
         mInjector.mElapsedRealtime = HOUR_MS;
@@ -882,6 +894,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testOverrides() throws Exception {
         // Can force to NEVER
         mInjector.mElapsedRealtime = HOUR_MS;
@@ -992,6 +1005,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testTimeout() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1015,6 +1029,7 @@
 
     /** Test that timeouts still work properly even if invalid configuration values are set. */
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testTimeout_InvalidThresholds() throws Exception {
         mInjector.mSettingsBuilder
                 .setLong("screen_threshold_active", -1)
@@ -1052,6 +1067,7 @@
      * timeout has passed.
      */
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testTimeoutBeforeRestricted() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1078,6 +1094,7 @@
      * Test that an app is put into the RESTRICTED bucket after enough time has passed.
      */
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testRestrictedDelay() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1100,6 +1117,7 @@
      * Test that an app is put into the RESTRICTED bucket after enough time has passed.
      */
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testRestrictedDelay_DelayChange() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1124,6 +1142,7 @@
      * a low bucket after the RESTRICTED timeout.
      */
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() throws Exception {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1160,6 +1179,7 @@
      * a low bucket after the RESTRICTED timeout.
      */
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testRestrictedTimeoutOverridesPredictionLowBucket() throws Exception {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
 
@@ -1187,6 +1207,7 @@
      * interaction.
      */
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSystemInteractionOverridesRestrictedTimeout() throws Exception {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1213,6 +1234,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testRestrictedBucketDisabled() throws Exception {
         mInjector.mIsRestrictedBucketEnabled = false;
         // Get the controller to read the new value. Capturing the ContentObserver isn't possible
@@ -1238,6 +1260,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testRestrictedBucket_EnabledToDisabled() throws Exception {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
         mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
@@ -1255,6 +1278,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
 
@@ -1272,6 +1296,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testPredictionRaiseFromRestrictedTimeout_lowBucket() throws Exception {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
 
@@ -1289,6 +1314,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testCascadingTimeouts() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1312,6 +1338,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testOverlappingTimeouts() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1343,6 +1370,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSystemInteractionTimeout() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         // Fast forward to RARE
@@ -1366,6 +1394,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testInitialForegroundServiceTimeout() throws Exception {
         mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
         // Make sure app is in NEVER bucket
@@ -1399,6 +1428,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testPredictionNotOverridden() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1424,8 +1454,8 @@
         assertBucket(STANDBY_BUCKET_ACTIVE);
     }
 
-    @Ignore
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testPredictionStrikesBack() throws Exception {
         reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
         assertBucket(STANDBY_BUCKET_ACTIVE);
@@ -1451,6 +1481,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSystemForcedFlags_NotAddedForUserForce() throws Exception {
         final int expectedReason = REASON_MAIN_FORCED_BY_USER;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
@@ -1465,6 +1496,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSystemForcedFlags_AddedForSystemForce() throws Exception {
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
                 REASON_MAIN_DEFAULT);
@@ -1487,6 +1519,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testSystemForcedFlags_SystemForceChangesBuckets() throws Exception {
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
                 REASON_MAIN_DEFAULT);
@@ -1524,6 +1557,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testRestrictApp_MainReason() throws Exception {
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
                 REASON_MAIN_DEFAULT);
@@ -1598,6 +1632,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testUserInteraction_CrossProfile() throws Exception {
         mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3};
         mInjector.mCrossProfileTargets = Arrays.asList(USER_HANDLE_USER2);
@@ -1621,6 +1656,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testUnexemptedSyncScheduled() throws Exception {
         rearmLatch(PACKAGE_1);
         mController.addListener(mListener);
@@ -1642,6 +1678,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185169504)
     public void testExemptedSyncScheduled() throws Exception {
         setAndAssertBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE, REASON_MAIN_FORCED_BY_SYSTEM);
         mInjector.mDeviceIdleMode = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index e367579..488875b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1854,7 +1854,7 @@
             doReturn(WindowManagerGlobal.ADD_STARTING_NOT_NEEDED).when(session).addToDisplay(
                     any() /* window */,  any() /* attrs */,
                     anyInt() /* viewVisibility */, anyInt() /* displayId */,
-                    any() /* requestedVisibility */, any() /* outInputChannel */,
+                    any() /* requestedVisibilities */, any() /* outInputChannel */,
                     any() /* outInsetsState */, any() /* outActiveControls */);
             mAtm.mWindowManager.mStartingSurfaceController
                     .createTaskSnapshotSurface(activity, snapshot);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index a0b5fed..1191035 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -42,6 +42,7 @@
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -120,6 +121,7 @@
 import android.view.ISystemGestureExclusionListener;
 import android.view.IWindowManager;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.SurfaceControl;
@@ -870,19 +872,6 @@
                 .setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null);
     }
 
-    @UseTestDisplay
-    @Test
-    public void testClearLastFocusWhenReparentingFocusedWindow() {
-        final DisplayContent defaultDisplay = mWm.getDefaultDisplayContentLocked();
-        final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
-                defaultDisplay, "window");
-        defaultDisplay.mLastFocus = window;
-        mDisplayContent.mCurrentFocus = window;
-        mDisplayContent.reParentWindowToken(window.mToken);
-
-        assertNull(defaultDisplay.mLastFocus);
-    }
-
     @Test
     public void testGetPreferredOptionsPanelGravityFromDifferentDisplays() {
         final DisplayContent portraitDisplay = createNewDisplay();
@@ -1217,10 +1206,10 @@
         win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
         win.getAttrs().privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
         win.getAttrs().insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-        final InsetsState requestedState = new InsetsState();
-        requestedState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
-        requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        win.updateRequestedVisibility(requestedState);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        win.setRequestedVisibilities(requestedVisibilities);
         win.mActivityRecord.mTargetSdk = P;
 
         performLayout(dc);
@@ -1748,6 +1737,31 @@
     }
 
     @Test
+    public void testFindScrollCaptureTargetWindow_secure() {
+        DisplayContent display = createNewDisplay();
+        Task rootTask = createTask(display);
+        Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+        WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window");
+        secureWindow.mAttrs.flags |= FLAG_SECURE;
+
+        WindowState result = display.findScrollCaptureTargetWindow(null,
+                ActivityTaskManager.INVALID_TASK_ID);
+        assertNull(result);
+    }
+
+    @Test
+    public void testFindScrollCaptureTargetWindow_secureTaskId() {
+        DisplayContent display = createNewDisplay();
+        Task rootTask = createTask(display);
+        Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+        WindowState secureWindow = createWindow(null, TYPE_APPLICATION, "Secure Window");
+        secureWindow.mAttrs.flags |= FLAG_SECURE;
+
+        WindowState result = display.findScrollCaptureTargetWindow(null,  task.mTaskId);
+        assertNull(result);
+    }
+
+    @Test
     public void testFindScrollCaptureTargetWindow_taskId() {
         DisplayContent display = createNewDisplay();
         Task rootTask = createTask(display);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 3741d49..3c7c4fd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -65,6 +65,7 @@
 import android.view.DisplayInfo;
 import android.view.Gravity;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.PrivacyIndicatorBounds;
 import android.view.RoundedCorners;
 import android.view.WindowInsets.Side;
@@ -478,9 +479,9 @@
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mDisplayContent.getInsetsStateController().getRawInsetsState()
                 .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
-        final InsetsState requestedState = new InsetsState();
-        requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        mWindow.updateRequestedVisibility(requestedState);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        mWindow.setRequestedVisibilities(requestedVisibilities);
         addWindowWithRawInsetsState(mWindow);
 
         mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
@@ -498,9 +499,9 @@
         mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
         mDisplayContent.getInsetsStateController().getRawInsetsState()
                 .getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
-        final InsetsState requestedState = new InsetsState();
-        requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        mWindow.updateRequestedVisibility(requestedState);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        mWindow.setRequestedVisibilities(requestedVisibilities);
         mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         addWindowWithRawInsetsState(mWindow);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 5b04c91..07d467b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -49,6 +49,7 @@
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import androidx.test.filters.SmallTest;
 
@@ -181,9 +182,9 @@
 
         // Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar.
         final WindowState fullscreenApp = addWindow(TYPE_APPLICATION, "fullscreenApp");
-        final InsetsState requestedState = new InsetsState();
-        requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        fullscreenApp.updateRequestedVisibility(requestedState);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        fullscreenApp.setRequestedVisibilities(requestedVisibilities);
 
         // Add a non-fullscreen dialog window.
         final WindowState dialog = addWindow(TYPE_APPLICATION, "dialog");
@@ -216,9 +217,9 @@
 
         // Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't.
         final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp");
-        final InsetsState newRequestedState = new InsetsState();
-        newRequestedState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        newFocusedFullscreenApp.updateRequestedVisibility(newRequestedState);
+        final InsetsVisibilities newRequestedVisibilities = new InsetsVisibilities();
+        newRequestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
+        newFocusedFullscreenApp.setRequestedVisibilities(newRequestedVisibilities);
         // Make sure status bar is hidden by previous insets state.
         mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp);
 
@@ -279,10 +280,10 @@
         doNothing().when(policy).startAnimation(anyBoolean(), any());
 
         // Make both system bars invisible.
-        final InsetsState requestedState = new InsetsState();
-        requestedState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        requestedState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
-        mAppWindow.updateRequestedVisibility(requestedState);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, false);
+        mAppWindow.setRequestedVisibilities(requestedVisibilities);
         policy.updateBarControlTarget(mAppWindow);
         waitUntilWindowAnimatorIdle();
         assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
@@ -373,7 +374,10 @@
         assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible());
         assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
 
-        mAppWindow.updateRequestedVisibility(state);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, true);
+        requestedVisibilities.setVisibility(ITYPE_NAVIGATION_BAR, true);
+        mAppWindow.setRequestedVisibilities(requestedVisibilities);
         policy.onInsetsModified(mAppWindow);
         waitUntilWindowAnimatorIdle();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index c483ae9..2987f94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -31,7 +31,7 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsSource;
-import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import androidx.test.filters.SmallTest;
 
@@ -203,9 +203,9 @@
         statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindow(statusBar, null, null);
         mProvider.updateControlForTarget(target, false /* force */);
-        InsetsState state = new InsetsState();
-        state.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        target.updateRequestedVisibility(state);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        target.setRequestedVisibilities(requestedVisibilities);
         mProvider.updateClientVisibility(target);
         assertFalse(mSource.isVisible());
     }
@@ -216,9 +216,9 @@
         final WindowState target = createWindow(null, TYPE_APPLICATION, "target");
         statusBar.getFrame().set(0, 0, 500, 100);
         mProvider.setWindow(statusBar, null, null);
-        InsetsState state = new InsetsState();
-        state.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        target.updateRequestedVisibility(state);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        target.setRequestedVisibilities(requestedVisibilities);
         mProvider.updateClientVisibility(target);
         assertTrue(mSource.isVisible());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 80961d7..f8c84df 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -48,6 +48,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 
 import androidx.test.filters.SmallTest;
 
@@ -174,10 +175,10 @@
         mImeWindow.setHasSurface(true);
         getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
         getController().onImeControlTargetChanged(mDisplayContent.getImeTarget(IME_TARGET_INPUT));
-        final InsetsState requestedState = new InsetsState();
-        requestedState.getSource(ITYPE_IME).setVisible(true);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_IME, true);
         mDisplayContent.getImeTarget(IME_TARGET_INPUT).getWindow()
-                .updateRequestedVisibility(requestedState);
+                .setRequestedVisibilities(requestedVisibilities);
         getController().onInsetsModified(mDisplayContent.getImeTarget(IME_TARGET_INPUT));
 
         // Send our spy window (app) into the system so that we can detect the invocation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 5af68021..e3c38b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -859,6 +859,40 @@
     }
 
     @Test
+    public void testFreezeTaskListOrder_replaceTask() {
+        // Create two tasks with the same affinity
+        Task affinityTask1 = createTaskBuilder(".AffinityTask1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .build();
+        Task affinityTask2 = createTaskBuilder(".AffinityTask2")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .build();
+        affinityTask2.affinity = affinityTask1.affinity = "affinity";
+
+        // Add some tasks
+        mRecentTasks.add(mTasks.get(0));
+        mRecentTasks.add(affinityTask1);
+        mRecentTasks.add(mTasks.get(1));
+        mCallbacksRecorder.clear();
+
+        // Freeze the list
+        mRecentTasks.setFreezeTaskListReordering();
+        assertTrue(mRecentTasks.isFreezeTaskListReorderingSet());
+
+        // Add the affinity task
+        mRecentTasks.add(affinityTask2);
+
+        assertRecentTasksOrder(mTasks.get(1),
+                affinityTask2,
+                mTasks.get(0));
+
+        assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+        assertThat(mCallbacksRecorder.mAdded).contains(affinityTask2);
+        assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
+        assertThat(mCallbacksRecorder.mRemoved).contains(affinityTask1);
+    }
+
+    @Test
     public void testFreezeTaskListOrder_timeout() {
         // Add some tasks
         mRecentTasks.add(mTasks.get(0));
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index e32b2aa..cac948c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -270,4 +270,10 @@
     public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) {
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction setTrustedOverlay(SurfaceControl sc,
+            boolean isTrustedOverlay) {
+        return this;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index f83f1cf..c35f317 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -41,6 +41,7 @@
 import android.window.TaskFragmentCreationParams;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizerToken;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransactionCallback;
@@ -62,6 +63,7 @@
 
     private TaskFragmentOrganizerController mController;
     private TaskFragmentOrganizer mOrganizer;
+    private TaskFragmentOrganizerToken mOrganizerToken;
     private ITaskFragmentOrganizer mIOrganizer;
     private TaskFragment mTaskFragment;
     private TaskFragmentInfo mTaskFragmentInfo;
@@ -73,7 +75,8 @@
     public void setup() {
         mController = mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
         mOrganizer = new TaskFragmentOrganizer(Runnable::run);
-        mIOrganizer = mOrganizer.getIOrganizer();
+        mOrganizerToken = mOrganizer.getOrganizerToken();
+        mIOrganizer = ITaskFragmentOrganizer.Stub.asInterface(mOrganizerToken.asBinder());
         mTaskFragmentInfo = mock(TaskFragmentInfo.class);
         mFragmentToken = new Binder();
         mTaskFragment =
@@ -235,7 +238,7 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
     }
@@ -257,7 +260,7 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
     }
@@ -280,7 +283,7 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
     }
@@ -306,8 +309,8 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
-        taskFragment2.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
+        taskFragment2.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
     }
@@ -317,7 +320,9 @@
         mOrganizer.applyTransaction(mTransaction);
 
         // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
-        mTransaction.createTaskFragment(mock(TaskFragmentCreationParams.class));
+        final TaskFragmentCreationParams mockParams = mock(TaskFragmentCreationParams.class);
+        doReturn(mOrganizerToken).when(mockParams).getOrganizer();
+        mTransaction.createTaskFragment(mockParams);
         mTransaction.startActivityInTaskFragment(
                 mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
         mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
@@ -352,7 +357,7 @@
         });
 
         // Allow transaction to change a TaskFragment created by the organizer.
-        mTaskFragment.setTaskFragmentOrganizer(mIOrganizer, 10 /* pid */);
+        mTaskFragment.setTaskFragmentOrganizer(mOrganizerToken, 10 /* pid */);
 
         mAtm.getWindowOrganizerController().applyTransaction(mTransaction);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
index efe6538..316309c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowFrameTests.java
@@ -35,6 +35,7 @@
 import android.view.Gravity;
 import android.view.InsetsSource;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.WindowManager;
 
 import androidx.test.filters.FlakyTest;
@@ -276,7 +277,9 @@
         imeFrame.top = 400;
         imeSource.setFrame(imeFrame);
         imeSource.setVisible(true);
-        w.updateRequestedVisibility(state);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_IME, true);
+        w.setRequestedVisibilities(requestedVisibilities);
         w.mAboveInsetsState.addSource(imeSource);
 
         // With no insets or system decor all the frames incoming from PhoneWindowManager
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 83cdc3ba..a91298f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -56,6 +56,7 @@
 import android.view.IWindowSessionCallback;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.WindowManager;
 
@@ -278,7 +279,7 @@
                 .getWindowType(eq(windowContextToken));
 
         mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
-                UserHandle.USER_SYSTEM, new InsetsState(), null, new InsetsState(),
+                UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(),
                 new InsetsSourceControl[0]);
 
         verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e83db78..17288c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -85,6 +85,7 @@
 import android.view.InputWindowHandle;
 import android.view.InsetsSource;
 import android.view.InsetsState;
+import android.view.InsetsVisibilities;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 
@@ -437,9 +438,9 @@
                 .setWindow(statusBar, null /* frameProvider */, null /* imeFrameProvider */);
         mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
                 app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
-        final InsetsState state = new InsetsState();
-        state.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        app.updateRequestedVisibility(state);
+        final InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        requestedVisibilities.setVisibility(ITYPE_STATUS_BAR, false);
+        app.setRequestedVisibilities(requestedVisibilities);
         mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
                 .updateClientVisibility(app);
         waitUntilHandlersIdle();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index e408cfc..965f126 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -87,14 +87,14 @@
  */
 final class HotwordDetectionConnection {
     private static final String TAG = "HotwordDetectionConnection";
-    // TODO (b/177502877): Set the Debug flag to false before shipping.
-    static final boolean DEBUG = true;
+    static final boolean DEBUG = false;
 
     // TODO: These constants need to be refined.
     private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
     private static final long MAX_UPDATE_TIMEOUT_MILLIS = 6000;
     private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
             Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
+    private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
 
     private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
     // TODO: This may need to be a Handler(looper)
@@ -103,6 +103,7 @@
     private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
     private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
     private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
+    private final IHotwordRecognitionStatusCallback mCallback;
 
     final Object mLock;
     final int mVoiceInteractionServiceUid;
@@ -110,11 +111,11 @@
     final int mUser;
     final Context mContext;
     volatile HotwordDetectionServiceIdentity mIdentity;
-    private IHotwordRecognitionStatusCallback mCallback;
     private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
     private Instant mLastRestartInstant;
 
     private ScheduledFuture<?> mCancellationTaskFuture;
+    private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
 
     /** Identity used for attributing app ops when delivering data to the Interactor. */
     @GuardedBy("mLock")
@@ -128,17 +129,24 @@
     private boolean mPerformingSoftwareHotwordDetection;
     private @NonNull ServiceConnection mRemoteHotwordDetectionService;
     private IBinder mAudioFlinger;
+    private boolean mDebugHotwordLogging = false;
 
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
             boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback) {
+            @Nullable SharedMemory sharedMemory,
+            @NonNull IHotwordRecognitionStatusCallback callback) {
+        if (callback == null) {
+            Slog.w(TAG, "Callback is null while creating connection");
+            throw new IllegalArgumentException("Callback is null while creating connection");
+        }
         mLock = lock;
         mContext = context;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
         mVoiceInteractorIdentity = voiceInteractorIdentity;
         mDetectionComponentName = serviceName;
         mUser = userId;
+        mCallback = callback;
         final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
         intent.setComponent(mDetectionComponentName);
         initAudioFlingerLocked();
@@ -147,22 +155,13 @@
 
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
 
-        if (callback == null) {
-            updateStateLocked(options, sharedMemory);
-            return;
-        }
-        mCallback = callback;
-
         mLastRestartInstant = Instant.now();
         updateStateAfterProcessStart(options, sharedMemory);
 
         // TODO(volnov): we need to be smarter here, e.g. schedule it a bit more often, but wait
         // until the current session is closed.
         mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {
-            if (DEBUG) {
-                Slog.i(TAG, "Time to restart the process, TTL has passed");
-            }
-
+            Slog.v(TAG, "Time to restart the process, TTL has passed");
             synchronized (mLock) {
                 restartProcessLocked();
             }
@@ -268,9 +267,9 @@
     }
 
     void cancelLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "cancelLocked");
-        }
+        Slog.v(TAG, "cancelLocked");
+        clearDebugHotwordLoggingTimeoutLocked();
+        mDebugHotwordLogging = false;
         if (mRemoteHotwordDetectionService.isBound()) {
             mRemoteHotwordDetectionService.unbind();
             LocalServices.getService(PermissionManagerServiceInternal.class)
@@ -288,6 +287,7 @@
         // TODO(b/191742511): this logic needs a test
         if (!mUpdateStateAfterStartFinished.get()
                 && Instant.now().minus(MAX_UPDATE_TIMEOUT_DURATION).isBefore(mLastRestartInstant)) {
+            Slog.v(TAG, "call updateStateAfterProcessStart");
             updateStateAfterProcessStart(options, sharedMemory);
         } else {
             mRemoteHotwordDetectionService.run(
@@ -330,6 +330,9 @@
                         if (result != null) {
                             Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
                                     + " bits from hotword trusted process");
+                            if (mDebugHotwordLogging) {
+                                Slog.i(TAG, "Egressed detected result: " + result);
+                            }
                         }
                     } else {
                         Slog.i(TAG, "Hotword detection has already completed");
@@ -407,15 +410,11 @@
 
     private void detectFromDspSourceForTest(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
             IHotwordRecognitionStatusCallback externalCallback) {
-        if (DEBUG) {
-            Slog.d(TAG, "detectFromDspSourceForTest");
-        }
+        Slog.v(TAG, "detectFromDspSourceForTest");
         IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
             @Override
             public void onDetected(HotwordDetectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onDetected");
-                }
+                Slog.v(TAG, "onDetected");
                 synchronized (mLock) {
                     if (mValidatingDspTrigger) {
                         mValidatingDspTrigger = false;
@@ -424,6 +423,9 @@
                         if (result != null) {
                             Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
                                     + " bits from hotword trusted process");
+                            if (mDebugHotwordLogging) {
+                                Slog.i(TAG, "Egressed detected result: " + result);
+                            }
                         }
                     } else {
                         Slog.i(TAG, "Ignored hotword detected since trigger has been handled");
@@ -433,13 +435,14 @@
 
             @Override
             public void onRejected(HotwordRejectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onRejected");
-                }
+                Slog.v(TAG, "onRejected");
                 synchronized (mLock) {
                     if (mValidatingDspTrigger) {
                         mValidatingDspTrigger = false;
                         externalCallback.onRejected(result);
+                        if (mDebugHotwordLogging && result != null) {
+                            Slog.i(TAG, "Egressed rejected result: " + result);
+                        }
                     } else {
                         Slog.i(TAG, "Ignored hotword rejected since trigger has been handled");
                     }
@@ -482,6 +485,9 @@
                     if (result != null) {
                         Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(result)
                                 + " bits from hotword trusted process");
+                        if (mDebugHotwordLogging) {
+                            Slog.i(TAG, "Egressed detected result: " + result);
+                        }
                     }
                 }
             }
@@ -498,6 +504,9 @@
                     }
                     mValidatingDspTrigger = false;
                     externalCallback.onRejected(result);
+                    if (mDebugHotwordLogging && result != null) {
+                        Slog.i(TAG, "Egressed rejected result: " + result);
+                    }
                 }
             }
         };
@@ -514,19 +523,37 @@
     }
 
     void forceRestart() {
-        if (DEBUG) {
-            Slog.i(TAG, "Requested to restart the service internally. Performing the restart");
-        }
+        Slog.v(TAG, "Requested to restart the service internally. Performing the restart");
         synchronized (mLock) {
             restartProcessLocked();
         }
     }
 
-    private void restartProcessLocked() {
-        if (DEBUG) {
-            Slog.i(TAG, "Restarting hotword detection process");
-        }
+    void setDebugHotwordLoggingLocked(boolean logging) {
+        Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
+        clearDebugHotwordLoggingTimeoutLocked();
+        mDebugHotwordLogging = logging;
 
+        if (logging) {
+            // Reset mDebugHotwordLogging to false after one hour
+            mDebugHotwordLoggingTimeoutFuture = mScheduledExecutorService.schedule(() -> {
+                Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
+                synchronized (mLock) {
+                    mDebugHotwordLogging = false;
+                }
+            }, RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private void clearDebugHotwordLoggingTimeoutLocked() {
+        if (mDebugHotwordLoggingTimeoutFuture != null) {
+            mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */true);
+            mDebugHotwordLoggingTimeoutFuture = null;
+        }
+    }
+
+    private void restartProcessLocked() {
+        Slog.v(TAG, "Restarting hotword detection process");
         ServiceConnection oldConnection = mRemoteHotwordDetectionService;
 
         // TODO(volnov): this can be done after connect() has been successful.
@@ -547,9 +574,7 @@
         // Recreate connection to reset the cache.
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
 
-        if (DEBUG) {
-            Slog.i(TAG, "Started the new process, issuing #onProcessRestarted");
-        }
+        Slog.v(TAG, "Started the new process, issuing #onProcessRestarted");
         try {
             mCallback.onProcessRestarted();
         } catch (RemoteException e) {
@@ -700,6 +725,9 @@
                                 bestEffortClose(serviceAudioSource);
                                 bestEffortClose(audioSource);
 
+                                if (mDebugHotwordLogging && result != null) {
+                                    Slog.i(TAG, "Egressed rejected result: " + result);
+                                }
                                 // TODO: Propagate the HotwordRejectedResult.
                             }
 
@@ -714,6 +742,9 @@
                                 if (triggerResult != null) {
                                     Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(
                                             triggerResult) + " bits from hotword trusted process");
+                                    if (mDebugHotwordLogging) {
+                                        Slog.i(TAG, "Egressed detected result: " + triggerResult);
+                                    }
                                 }
                                 // TODO: Add a delay before closing.
                                 bestEffortClose(audioSource);
@@ -773,9 +804,7 @@
             }
             synchronized (mLock) {
                 if (!mRespectServiceConnectionStatusChanged) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignored onServiceConnectionStatusChanged event");
-                    }
+                    Slog.v(TAG, "Ignored onServiceConnectionStatusChanged event");
                     return;
                 }
                 mIsBound = connected;
@@ -792,9 +821,7 @@
             super.binderDied();
             synchronized (mLock) {
                 if (!mRespectServiceConnectionStatusChanged) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Ignored #binderDied event");
-                    }
+                    Slog.v(TAG, "Ignored #binderDied event");
                     return;
                 }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index ccf4267..71541ad 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -832,6 +832,17 @@
             mImpl.forceRestartHotwordDetector();
         }
 
+        // Called by Shell command
+        void setDebugHotwordLogging(boolean logging) {
+            synchronized (this) {
+                if (mImpl == null) {
+                    Slog.w(TAG, "setTemporaryLogging without running voice interaction service");
+                    return;
+                }
+                mImpl.setDebugHotwordLoggingLocked(logging);
+            }
+        }
+
         @Override
         public void showSession(Bundle args, int flags) {
             synchronized (this) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index cbcbf52..558a9ac 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -79,8 +79,7 @@
 
 class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConnection.Callback {
     final static String TAG = "VoiceInteractionServiceManager";
-    // TODO (b/177502877): Set the Debug flag to false before shipping.
-    static final boolean DEBUG = true;
+    static final boolean DEBUG = false;
 
     final static String CLOSE_REASON_VOICE_INTERACTION = "voiceinteraction";
 
@@ -420,9 +419,7 @@
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
             IHotwordRecognitionStatusCallback callback) {
-        if (DEBUG) {
-            Slog.d(TAG, "updateStateLocked");
-        }
+        Slog.v(TAG, "updateStateLocked");
         if (mHotwordDetectionComponentName == null) {
             Slog.w(TAG, "Hotword detection service name not found");
             throw new IllegalStateException("Hotword detection service name not found");
@@ -584,6 +581,14 @@
         mHotwordDetectionConnection.forceRestart();
     }
 
+    void setDebugHotwordLoggingLocked(boolean logging) {
+        if (mHotwordDetectionConnection == null) {
+            Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
+            return;
+        }
+        mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
+    }
+
     void resetHotwordDetectionConnectionLocked() {
         if (DEBUG) {
             Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
index cdd8f7b..9bdf4e4 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceShellCommand.java
@@ -56,6 +56,8 @@
                 return requestDisable(pw);
             case "restart-detection":
                 return requestRestartDetection(pw);
+            case "set-debug-hotword-logging":
+                return setDebugHotwordLogging(pw);
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -76,9 +78,14 @@
             pw.println("");
             pw.println("  disable [true|false]");
             pw.println("    Temporarily disable (when true) service");
+            pw.println("");
             pw.println("  restart-detection");
             pw.println("    Force a restart of a hotword detection service");
             pw.println("");
+            pw.println("  set-debug-hotword-logging [true|false]");
+            pw.println("    Temporarily enable or disable debug logging for hotword result.");
+            pw.println("    The debug logging will be reset after one hour from last enable.");
+            pw.println("");
         }
     }
 
@@ -157,6 +164,17 @@
         return 0;
     }
 
+    private int setDebugHotwordLogging(PrintWriter pw) {
+        boolean logging = Boolean.parseBoolean(getNextArgRequired());
+        Slog.i(TAG, "setDebugHotwordLogging(): " + logging);
+        try {
+            mService.setDebugHotwordLogging(logging);
+        } catch (Exception e) {
+            return handleError(pw, "setDebugHotwordLogging()", e);
+        }
+        return 0;
+    }
+
     private static int handleError(PrintWriter pw, String message, Exception e) {
         Slog.e(TAG,  "error calling " + message, e);
         pw.printf("Error calling %s: %s\n", message, e);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f01519f..72ad23b 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3538,7 +3538,7 @@
             "nr_advanced_capable_pco_id_int";
 
     /**
-     * This configuration allows the framework to use user data communication to detect RRC state,
+     * This configuration allows the framework to use user data communication to detect Idle state,
      * and this is used on the 5G icon.
      *
      * There is a new way for for RRC state detection at Android 12. If
@@ -3546,16 +3546,23 @@
      * {@link TelephonyManager#CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED}) returns true,
      * then framework can use PHYSICAL_CHANNEL_CONFIG for RRC state detection. Based on this
      * condition, some carriers want to use the legacy behavior that way is using user data
-     * communication to detect the RRC state. Therefore, this configuration allows the framework
-     * to use user data communication to detect RRC state.
+     * communication to detect the Idle state. Therefore, this configuration allows the framework
+     * to use user data communication to detect Idle state.
      *
-     * The precondition is
+     * There are 3 situations reflects the carrier define Idle state.
+     * 1. using PHYSICAL_CHANNEL_CONFIG to detect RRC Idle
+     * 2. using all of data connections to detect RRC Idle.
+     * 3. using data communication(consider internet data connection only) to detect data Idle.
+     *
+     * How to setup for above 3 cases?
+     * For below part, we call the condition#1 is device support
      * {@link android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported}(
-     * {@link TelephonyManager#CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED}) returns true,
-     * otherwise this config is not working.
-     * If this is true, framework uses the user data communication for RRC state detection.
-     * If this is false, framework uses the PHYSICAL_CHANNEL_CONFIG for RRC state detection.
+     * {@link TelephonyManager#CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED}).
+     * The condition#2 is carrier enable the KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL.
      *
+     * For case#1, the condition#1 is true and the condition#2 is false.
+     * For case#2, the condition#1 is false and the condition#2 is false.
+     * For case#3, the condition#2 is true.
      * @hide
      */
     public static final String KEY_LTE_ENDC_USING_USER_DATA_FOR_RRC_DETECTION_BOOL =
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index 72150dd..6ada32e 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -178,7 +178,7 @@
         mCsiSinr = inRangeOrUnavailable(csiSinr, -23, 23);
         mCsiCqiTableIndex = inRangeOrUnavailable(csiCqiTableIndex, 1, 3);
         mCsiCqiReport = csiCqiReport.stream()
-                .map(cqi -> new Integer(inRangeOrUnavailable(Byte.toUnsignedInt(cqi), 1, 3)))
+                .map(cqi -> new Integer(inRangeOrUnavailable(Byte.toUnsignedInt(cqi), 0, 15)))
                 .collect(Collectors.toList());
         mSsRsrp = inRangeOrUnavailable(ssRsrp, -140, -44);
         mSsRsrq = inRangeOrUnavailable(ssRsrq, -43, 20);