Merge "Promote or demote stable and flaky flicker tests"
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 5ab7ff9..b101895 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -51,6 +51,7 @@
     ],
     libs: [
         "framework-appsearch.impl",
+        "framework-statsd.stubs.module_lib",
         "unsupportedappusage", // TODO(b/181887768) should be removed
     ],
     defaults: ["framework-system-server-module-defaults"],
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 689aa1f..c44fd40 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchConfig.java
@@ -64,6 +64,12 @@
     static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES = 512 * 1024; // 512KiB
     @VisibleForTesting
     static final int DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT = 20_000;
+    @VisibleForTesting
+    static final int DEFAULT_BYTES_OPTIMIZE_THRESHOLD = 1 * 1024 * 1024; // 1 MiB
+    @VisibleForTesting
+    static final int DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS = Integer.MAX_VALUE;
+    @VisibleForTesting
+    static final int DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD = 10_000;
 
     /*
      * Keys for ALL the flags stored in DeviceConfig.
@@ -79,6 +85,9 @@
             "limit_config_max_document_size_bytes";
     public static final String KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT =
             "limit_config_max_document_docunt";
+    public static final String KEY_BYTES_OPTIMIZE_THRESHOLD = "bytes_optimize_threshold";
+    public static final String KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS = "time_optimize_threshold";
+    public static final String KEY_DOC_COUNT_OPTIMIZE_THRESHOLD = "doc_count_optimize_threshold";
 
     // Array contains all the corresponding keys for the cached values.
     private static final String[] KEYS_TO_ALL_CACHED_VALUES = {
@@ -88,6 +97,9 @@
             KEY_SAMPLING_INTERVAL_FOR_PUT_DOCUMENT_STATS,
             KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
             KEY_LIMIT_CONFIG_MAX_DOCUMENT_COUNT,
+            KEY_BYTES_OPTIMIZE_THRESHOLD,
+            KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
+            KEY_DOC_COUNT_OPTIMIZE_THRESHOLD
     };
 
     // Lock needed for all the operations in this class.
@@ -251,6 +263,48 @@
         }
     }
 
+    /**
+     * Returns the cached optimize byte size threshold.
+     *
+     * An AppSearch Optimize job will be triggered if the bytes size of garbage resource exceeds
+     * this threshold.
+     */
+    int getCachedBytesOptimizeThreshold() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_BYTES_OPTIMIZE_THRESHOLD,
+                    DEFAULT_BYTES_OPTIMIZE_THRESHOLD);
+        }
+    }
+
+    /**
+     * Returns the cached optimize time interval threshold.
+     *
+     * An AppSearch Optimize job will be triggered if the time since last optimize job exceeds
+     * this threshold.
+     */
+    int getCachedTimeOptimizeThresholdMs() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
+                    DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS);
+        }
+    }
+
+    /**
+     * Returns the cached optimize document count threshold threshold.
+     *
+     * An AppSearch Optimize job will be triggered if the number of document of garbage resource
+     * exceeds this threshold.
+     */
+    int getCachedDocCountOptimizeThreshold() {
+        synchronized (mLock) {
+            throwIfClosedLocked();
+            return mBundleLocked.getInt(KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
+                    DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD);
+        }
+    }
+
     @GuardedBy("mLock")
     private void throwIfClosedLocked() {
         if (mIsClosedLocked) {
@@ -307,6 +361,24 @@
                             properties.getInt(key, DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT));
                 }
                 break;
+            case KEY_BYTES_OPTIMIZE_THRESHOLD:
+                synchronized (mLock) {
+                    mBundleLocked.putInt(key, properties.getInt(key,
+                            DEFAULT_BYTES_OPTIMIZE_THRESHOLD));
+                }
+                break;
+            case KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS:
+                synchronized (mLock) {
+                    mBundleLocked.putInt(key, properties.getInt(key,
+                            DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS));
+                }
+                break;
+            case KEY_DOC_COUNT_OPTIMIZE_THRESHOLD:
+                synchronized (mLock) {
+                    mBundleLocked.putInt(key, properties.getInt(key,
+                            DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD));
+                }
+                break;
             default:
                 break;
         }
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 ec37c3f..b52a503 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -61,6 +61,7 @@
 import com.android.server.SystemService;
 import com.android.server.appsearch.external.localstorage.stats.CallStats;
 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
+import com.android.server.appsearch.stats.StatsCollector;
 import com.android.server.appsearch.util.PackageUtil;
 import com.android.server.usage.StorageStatsManagerLocal;
 import com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
@@ -123,6 +124,13 @@
                 .registerStorageStatsAugmenter(new AppSearchStorageStatsAugmenter(), TAG);
     }
 
+    @Override
+    public void onBootPhase(/* @BootPhase */ int phase) {
+        if (phase == PHASE_BOOT_COMPLETED) {
+            StatsCollector.getInstance(mContext, EXECUTOR);
+        }
+    }
+
     private void registerReceivers() {
         mContext.registerReceiverForAllUsers(
                 new UserActionReceiver(),
@@ -364,6 +372,12 @@
                     ++operationSuccessCount;
                     invokeCallbackOnResult(callback,
                             AppSearchResult.newSuccessfulResult(setSchemaResponse.getBundle()));
+
+                    // setSchema will sync the schemas in the request to AppSearch, any existing
+                    // schemas which  is not included in the request will be delete if we force
+                    // override incompatible schemas. And all documents of these types will be
+                    // deleted as well. We should checkForOptimize for these deletion.
+                    checkForOptimize(instance);
                 } catch (Throwable t) {
                     ++operationFailureCount;
                     statusCode = throwableToFailedResult(t).getResultCode();
@@ -505,6 +519,10 @@
                     // Now that the batch has been written. Persist the newly written data.
                     instance.getAppSearchImpl().persistToDisk(PersistType.Code.LITE);
                     invokeCallbackOnResult(callback, resultBuilder.build());
+
+                    // The existing documents with same ID will be deleted, so there may be some
+                    // resources that could be released after optimize().
+                    checkForOptimize(instance, /*mutateBatchSize=*/ documentBundles.size());
                 } catch (Throwable t) {
                     ++operationFailureCount;
                     statusCode = throwableToFailedResult(t).getResultCode();
@@ -1023,6 +1041,8 @@
                     // Now that the batch has been written. Persist the newly written data.
                     instance.getAppSearchImpl().persistToDisk(PersistType.Code.LITE);
                     invokeCallbackOnResult(callback, resultBuilder.build());
+
+                    checkForOptimize(instance, ids.size());
                 } catch (Throwable t) {
                     ++operationFailureCount;
                     statusCode = throwableToFailedResult(t).getResultCode();
@@ -1092,6 +1112,8 @@
                     instance.getAppSearchImpl().persistToDisk(PersistType.Code.LITE);
                     ++operationSuccessCount;
                     invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
+
+                    checkForOptimize(instance);
                 } catch (Throwable t) {
                     ++operationFailureCount;
                     statusCode = throwableToFailedResult(t).getResultCode();
@@ -1472,4 +1494,24 @@
             }
         }
     }
+
+    private void checkForOptimize(AppSearchUserInstance instance, int mutateBatchSize) {
+        EXECUTOR.execute(() -> {
+            try {
+                instance.getAppSearchImpl().checkForOptimize(mutateBatchSize);
+            } catch (AppSearchException e) {
+                Log.w(TAG, "Error occurred when check for optimize", e);
+            }
+        });
+    }
+
+    private void checkForOptimize(AppSearchUserInstance instance) {
+        EXECUTOR.execute(() -> {
+            try {
+                instance.getAppSearchImpl().checkForOptimize();
+            } catch (AppSearchException e) {
+                Log.w(TAG, "Error occurred when check for optimize", e);
+            }
+        });
+    }
 }
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchUserInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchUserInstanceManager.java
index d0d2e89..529f2b0 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchUserInstanceManager.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchUserInstanceManager.java
@@ -27,12 +27,13 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
-import com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy;
 import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
 import com.android.server.appsearch.stats.PlatformLogger;
 import com.android.server.appsearch.visibilitystore.VisibilityStoreImpl;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -158,6 +159,18 @@
         }
     }
 
+    /**
+     * Returns the list of all {@link UserHandle}s.
+     *
+     * <p>It can return an empty list if there is no {@link AppSearchUserInstance} created yet.
+     */
+    @NonNull
+    public List<UserHandle> getAllUserHandles() {
+        synchronized (mInstancesLocked) {
+            return new ArrayList<>(mInstancesLocked.keySet());
+        }
+    }
+
     @NonNull
     private AppSearchUserInstance createUserInstance(
             @NonNull Context userContext,
@@ -177,7 +190,7 @@
                 icingDir,
                 new FrameworkLimitConfig(config),
                 initStatsBuilder,
-                new FrameworkOptimizeStrategy());
+                new FrameworkOptimizeStrategy(config));
 
         long prepareVisibilityStoreLatencyStartMillis = SystemClock.elapsedRealtime();
         VisibilityStoreImpl visibilityStore =
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/FrameworkOptimizeStrategy.java b/apex/appsearch/service/java/com/android/server/appsearch/FrameworkOptimizeStrategy.java
new file mode 100644
index 0000000..d934449
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/FrameworkOptimizeStrategy.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.appsearch;
+
+import android.annotation.NonNull;
+
+import com.android.server.appsearch.external.localstorage.AppSearchImpl;
+import com.android.server.appsearch.external.localstorage.OptimizeStrategy;
+
+import com.google.android.icing.proto.GetOptimizeInfoResultProto;
+
+import java.util.Objects;
+
+/**
+ * An implementation of {@link OptimizeStrategy} will determine when to trigger {@link
+ * AppSearchImpl#optimize()} in Jetpack environment.
+ *
+ * @hide
+ */
+public class FrameworkOptimizeStrategy implements OptimizeStrategy {
+    private final AppSearchConfig mAppSearchConfig;
+    FrameworkOptimizeStrategy(@NonNull AppSearchConfig config) {
+        mAppSearchConfig = Objects.requireNonNull(config);
+    }
+
+    @Override
+    public boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo) {
+        return optimizeInfo.getOptimizableDocs()
+                    >= mAppSearchConfig.getCachedDocCountOptimizeThreshold()
+                || optimizeInfo.getEstimatedOptimizableBytes()
+                    >= mAppSearchConfig.getCachedBytesOptimizeThreshold()
+                || optimizeInfo.getTimeSinceLastOptimizeMs()
+                    >= mAppSearchConfig.getCachedTimeOptimizeThresholdMs();
+    }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java
deleted file mode 100644
index 8ec30e1..0000000
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategy.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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;
-
-import android.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import com.google.android.icing.proto.GetOptimizeInfoResultProto;
-
-/**
- * An implementation of {@link OptimizeStrategy} will determine when to trigger {@link
- * AppSearchImpl#optimize()} in Jetpack environment.
- *
- * @hide
- */
-public class FrameworkOptimizeStrategy implements OptimizeStrategy {
-
-    @VisibleForTesting static final int DOC_COUNT_OPTIMIZE_THRESHOLD = 100_000;
-    @VisibleForTesting static final int BYTES_OPTIMIZE_THRESHOLD = 1 * 1024 * 1024 * 1024; // 1GB
-
-    @VisibleForTesting
-    static final long TIME_OPTIMIZE_THRESHOLD_MILLIS = 7 * 24 * 60 * 60 * 1000; // 1 week
-
-    @Override
-    public boolean shouldOptimize(@NonNull GetOptimizeInfoResultProto optimizeInfo) {
-        return optimizeInfo.getOptimizableDocs() >= DOC_COUNT_OPTIMIZE_THRESHOLD
-                || optimizeInfo.getEstimatedOptimizableBytes() >= BYTES_OPTIMIZE_THRESHOLD
-                || optimizeInfo.getTimeSinceLastOptimizeMs() >= TIME_OPTIMIZE_THRESHOLD_MILLIS;
-    }
-}
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 5371478..2cbce10 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
@@ -45,7 +45,7 @@
 import java.util.Random;
 
 /**
- * Logger Implementation to log to statsd.
+ * Logger Implementation for pushed atoms.
  *
  * <p>This class is thread-safe.
  *
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/StatsCollector.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/StatsCollector.java
new file mode 100644
index 0000000..dd56739
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/StatsCollector.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.appsearch.stats;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.StatsManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.StatsEvent;
+
+import com.android.server.appsearch.AppSearchUserInstance;
+import com.android.server.appsearch.AppSearchUserInstanceManager;
+
+import com.google.android.icing.proto.DocumentStorageInfoProto;
+import com.google.android.icing.proto.IndexStorageInfoProto;
+import com.google.android.icing.proto.SchemaStoreStorageInfoProto;
+import com.google.android.icing.proto.StorageInfoProto;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Implements statsd pullers for AppSearch.
+ *
+ * <p>This class registers pullers to statsd, which will be called once a day to obtain AppSearch
+ * statistics that cannot be sent to statsd in real time by {@link PlatformLogger}.
+ *
+ * @hide
+ */
+public final class StatsCollector implements StatsManager.StatsPullAtomCallback {
+    private static final String TAG = "AppSearchStatsCollector";
+
+    private static volatile StatsCollector sStatsCollector;
+    private final StatsManager mStatsManager;
+
+    /**
+     * Gets an instance of {@link StatsCollector} to be used.
+     *
+     * <p>If no instance has been initialized yet, a new one will be created. Otherwise, the
+     * existing instance will be returned.
+     */
+    @NonNull
+    public static StatsCollector getInstance(@NonNull Context context,
+            @NonNull Executor executor) {
+        Objects.requireNonNull(context);
+        Objects.requireNonNull(executor);
+        if (sStatsCollector == null) {
+            synchronized (StatsCollector.class) {
+                if (sStatsCollector == null) {
+                    sStatsCollector = new StatsCollector(context, executor);
+                }
+            }
+        }
+        return sStatsCollector;
+    }
+
+    private StatsCollector(@NonNull Context context, @NonNull Executor executor) {
+        mStatsManager = context.getSystemService(StatsManager.class);
+        if (mStatsManager != null) {
+            registerAtom(AppSearchStatsLog.APP_SEARCH_STORAGE_INFO, /*policy=*/ null, executor);
+            Log.d(TAG, "atoms registered");
+        } else {
+            Log.e(TAG, "could not get StatsManager, atoms not registered");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return {@link StatsManager#PULL_SUCCESS} with list of atoms (potentially empty) if pull
+     * succeeded, {@link StatsManager#PULL_SKIP} if pull was too frequent or atom ID is
+     * unexpected.
+     */
+    @Override
+    public int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
+        Objects.requireNonNull(data);
+        switch (atomTag) {
+            case AppSearchStatsLog.APP_SEARCH_STORAGE_INFO:
+                return pullAppSearchStorageInfo(data);
+            default:
+                Log.e(TAG, "unexpected atom ID " + atomTag);
+                return StatsManager.PULL_SKIP;
+        }
+    }
+
+    private static int pullAppSearchStorageInfo(@NonNull List<StatsEvent> data) {
+        AppSearchUserInstanceManager userInstanceManager =
+                AppSearchUserInstanceManager.getInstance();
+        List<UserHandle> userHandles = userInstanceManager.getAllUserHandles();
+        for (int i = 0; i < userHandles.size(); i++) {
+            UserHandle userHandle = userHandles.get(i);
+            try {
+                AppSearchUserInstance userInstance = userInstanceManager.getUserInstance(
+                        userHandle);
+                StorageInfoProto storageInfoProto =
+                        userInstance.getAppSearchImpl().getRawStorageInfoProto();
+                data.add(buildStatsEvent(userHandle.getIdentifier(), storageInfoProto));
+            } catch (Throwable t) {
+                Log.e(TAG,
+                        "Failed to pull the storage info for user " + userHandle.toString(),
+                        t);
+            }
+        }
+
+        // Skip the report if there is no data.
+        if (data.isEmpty()) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        return StatsManager.PULL_SUCCESS;
+    }
+
+    /**
+     * Registers and configures the callback for the pulled atom.
+     *
+     * @param atomId   The id of the atom
+     * @param policy   Optional metadata specifying the timeout, cool down time etc. statsD would
+     *                 use default values if it is null
+     * @param executor The executor in which to run the callback
+     */
+    private void registerAtom(int atomId, @Nullable StatsManager.PullAtomMetadata policy,
+            @NonNull Executor executor) {
+        mStatsManager.setPullAtomCallback(atomId, policy, executor, /*callback=*/this);
+    }
+
+    private static StatsEvent buildStatsEvent(@UserIdInt int userId,
+            @NonNull StorageInfoProto storageInfoProto) {
+        return AppSearchStatsLog.buildStatsEvent(
+                AppSearchStatsLog.APP_SEARCH_STORAGE_INFO,
+                userId,
+                storageInfoProto.getTotalStorageSize(),
+                getDocumentStorageInfoBytes(storageInfoProto.getDocumentStorageInfo()),
+                getSchemaStoreStorageInfoBytes(storageInfoProto.getSchemaStoreStorageInfo()),
+                getIndexStorageInfoBytes(storageInfoProto.getIndexStorageInfo()));
+    }
+
+    private static byte[] getDocumentStorageInfoBytes(
+            @NonNull DocumentStorageInfoProto proto) {
+        // Make sure we only log the fields defined in the atom in case new fields are added in
+        // IcingLib
+        DocumentStorageInfoProto.Builder builder = DocumentStorageInfoProto.newBuilder();
+        builder.setNumAliveDocuments(proto.getNumAliveDocuments())
+                .setNumDeletedDocuments(proto.getNumDeletedDocuments())
+                .setNumExpiredDocuments(proto.getNumExpiredDocuments())
+                .setDocumentStoreSize(proto.getDocumentStoreSize())
+                .setDocumentLogSize(proto.getDocumentLogSize())
+                .setKeyMapperSize(proto.getKeyMapperSize())
+                .setDocumentIdMapperSize(proto.getDocumentIdMapperSize())
+                .setScoreCacheSize(proto.getScoreCacheSize())
+                .setFilterCacheSize(proto.getFilterCacheSize())
+                .setCorpusMapperSize(proto.getCorpusMapperSize())
+                .setCorpusScoreCacheSize(proto.getCorpusScoreCacheSize())
+                .setNamespaceIdMapperSize(proto.getNamespaceIdMapperSize())
+                .setNumNamespaces(proto.getNumNamespaces());
+        return builder.build().toByteArray();
+    }
+
+    private static byte[] getSchemaStoreStorageInfoBytes(
+            @NonNull SchemaStoreStorageInfoProto proto) {
+        // Make sure we only log the fields defined in the atom in case new fields are added in
+        // IcingLib
+        SchemaStoreStorageInfoProto.Builder builder = SchemaStoreStorageInfoProto.newBuilder();
+        builder.setSchemaStoreSize(proto.getSchemaStoreSize())
+                .setNumSchemaTypes(proto.getNumSchemaTypes())
+                .setNumTotalSections(proto.getNumTotalSections())
+                .setNumSchemaTypesSectionsExhausted(proto.getNumSchemaTypesSectionsExhausted());
+        return builder.build().toByteArray();
+    }
+
+    private static byte[] getIndexStorageInfoBytes(
+            @NonNull IndexStorageInfoProto proto) {
+        // Make sure we only log the fields defined in the atom in case new fields are added in
+        // IcingLib
+        IndexStorageInfoProto.Builder builder = IndexStorageInfoProto.newBuilder();
+        builder.setIndexSize(proto.getIndexSize())
+                .setLiteIndexLexiconSize(proto.getLiteIndexLexiconSize())
+                .setLiteIndexHitBufferSize(proto.getLiteIndexHitBufferSize())
+                .setMainIndexLexiconSize(proto.getMainIndexLexiconSize())
+                .setMainIndexStorageSize(proto.getMainIndexStorageSize())
+                .setMainIndexBlockSize(proto.getMainIndexBlockSize())
+                .setNumBlocks(proto.getNumBlocks())
+                .setMinFreeFraction(proto.getMinFreeFraction());
+        return builder.build().toByteArray();
+    }
+}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 9dea181..6518c18 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2085,7 +2085,7 @@
   public final class PermissionManager {
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
-    method public void registerAttributionSource(@NonNull android.content.AttributionSource);
+    method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
   }
 
 }
@@ -2184,7 +2184,6 @@
     field @Deprecated public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
     field public static final String ENABLED_VR_LISTENERS = "enabled_vr_listeners";
     field public static final String IMMERSIVE_MODE_CONFIRMATIONS = "immersive_mode_confirmations";
-    field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
     field public static final String NOTIFICATION_BADGING = "notification_badging";
     field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
     field public static final String POWER_MENU_LOCKED_SHOW_CONTENT = "power_menu_locked_show_content";
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 6496303..24fb76a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3149,7 +3149,8 @@
         // If we want to access protected data on behalf of another app we need to
         // tell the OS that we opt in to participate in the attribution chain.
         if (nextAttributionSource != null) {
-            getSystemService(PermissionManager.class).registerAttributionSource(attributionSource);
+            attributionSource = getSystemService(PermissionManager.class)
+                    .registerAttributionSource(attributionSource);
         }
         return attributionSource;
     }
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index c499f69..d63ce0f 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -88,6 +88,8 @@
 public final class AttributionSource implements Parcelable {
     private static final String DESCRIPTOR = "android.content.AttributionSource";
 
+    private static final Binder sDefaultToken = new Binder(DESCRIPTOR);
+
     private final @NonNull AttributionSourceState mAttributionSourceState;
 
     private @Nullable AttributionSource mNextCached;
@@ -97,7 +99,7 @@
     @TestApi
     public AttributionSource(int uid, @Nullable String packageName,
             @Nullable String attributionTag) {
-        this(uid, packageName, attributionTag, new Binder(DESCRIPTOR));
+        this(uid, packageName, attributionTag, sDefaultToken);
     }
 
     /** @hide */
@@ -132,7 +134,7 @@
 
     AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
             @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
-        this(uid, packageName, attributionTag, new Binder(DESCRIPTOR), renouncedPermissions, next);
+        this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
     }
 
     AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
@@ -170,6 +172,12 @@
     }
 
     /** @hide */
+    public AttributionSource withToken(@NonNull Binder token) {
+        return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+                token, mAttributionSourceState.renouncedPermissions, getNext());
+    }
+
+    /** @hide */
     public @NonNull AttributionSourceState asState() {
         return mAttributionSourceState;
     }
@@ -543,7 +551,9 @@
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mAttributionSourceState.next = null;
             }
-            mAttributionSourceState.token = new Binder(DESCRIPTOR);
+
+            mAttributionSourceState.token = sDefaultToken;
+
             if (mAttributionSourceState.next == null) {
                 // The NDK aidl backend doesn't support null parcelable arrays.
                 mAttributionSourceState.next = new AttributionSourceState[0];
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 09fe102..6cbe107 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1869,40 +1869,28 @@
 
     @FastNative
     private static native void nativeUpdate(long dst, long src);
-    @FastNative
-    private static native void nativeWriteToParcel(Parcel dest, long ptr);
-    @FastNative
-    private static native void nativeReadFromParcel(Parcel source, long ptr);
-    @FastNative
-    private static native void nativeSwap(long ptr, long otherPtr)
+    private static synchronized native void nativeWriteToParcel(Parcel dest, long ptr);
+    private static synchronized native void nativeReadFromParcel(Parcel source, long ptr);
+    private static synchronized native void nativeSwap(long ptr, long otherPtr)
             throws NullPointerException;
     @FastNative
-    private static native void nativeClose(long ptr);
-    @FastNative
-    private static native boolean nativeIsEmpty(long ptr);
-    @FastNative
-    private static native int nativeGetEntryCount(long ptr);
-    @FastNative
-    private static native long nativeGetBufferSize(long ptr);
-    @FastNative
     private static native void nativeSetVendorId(long ptr, long vendorId);
+    private static synchronized native void nativeClose(long ptr);
+    private static synchronized native boolean nativeIsEmpty(long ptr);
+    private static synchronized native int nativeGetEntryCount(long ptr);
+    private static synchronized native long nativeGetBufferSize(long ptr);
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @FastNative
-    private static native byte[] nativeReadValues(int tag, long ptr);
-    @FastNative
-    private static native void nativeWriteValues(int tag, byte[] src, long ptr);
+    private static synchronized native byte[] nativeReadValues(int tag, long ptr);
+    private static synchronized native void nativeWriteValues(int tag, byte[] src, long ptr);
     private static synchronized native void nativeDump(long ptr) throws IOException; // dump to LOGD
 
-    @FastNative
-    private static native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
+    private static synchronized native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @FastNative
-    private static native int nativeGetTagFromKeyLocal(long ptr, String keyName)
+    private static synchronized native int nativeGetTagFromKeyLocal(long ptr, String keyName)
             throws IllegalArgumentException;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    @FastNative
-    private static native int nativeGetTypeFromTagLocal(long ptr, int tag)
+    private static synchronized native int nativeGetTypeFromTagLocal(long ptr, int tag)
             throws IllegalArgumentException;
     @FastNative
     private static native int nativeGetTagFromKey(String keyName, long vendorId)
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 4ef0e6e..a52ede8 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -44,6 +44,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.media.AudioManager;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
@@ -1163,18 +1164,24 @@
      * that doesn't participate in an attribution chain.
      *
      * @param source The attribution source to register.
+     * @return The registered new attribution source.
      *
      * @see #isRegisteredAttributionSource(AttributionSource)
      *
      * @hide
      */
     @TestApi
-    public void registerAttributionSource(@NonNull AttributionSource source) {
+    public @NonNull AttributionSource registerAttributionSource(@NonNull AttributionSource source) {
+        // We use a shared static token for sources that are not registered since the token's
+        // only used for process death detection. If we are about to use the source for security
+        // enforcement we need to replace the binder with a unique one.
+        final AttributionSource registeredSource = source.withToken(new Binder());
         try {
-            mPermissionManager.registerAttributionSource(source);
+            mPermissionManager.registerAttributionSource(registeredSource);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
+        return registeredSource;
     }
 
     /**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9a6f9fc..d2588a3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8749,8 +8749,6 @@
          * @hide
          */
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-        @TestApi
-        @Readable
         public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component";
 
         /**
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 145607a..6f915c9 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -128,6 +128,10 @@
          */
         @Appearance int getSystemBarsAppearance();
 
+        default boolean isSystemBarsAppearanceControlled() {
+            return false;
+        }
+
         /**
          * @see WindowInsetsController#setSystemBarsBehavior
          */
@@ -138,6 +142,10 @@
          */
         @Behavior int getSystemBarsBehavior();
 
+        default boolean isSystemBarsBehaviorControlled() {
+            return false;
+        }
+
         /**
          * Releases a surface and ensure that this is done after {@link #applySurfaceParams} has
          * finished applying params.
@@ -1520,6 +1528,10 @@
 
     @Override
     public @Appearance int getSystemBarsAppearance() {
+        if (!mHost.isSystemBarsAppearanceControlled()) {
+            // We only return the requested appearance, not the implied one.
+            return 0;
+        }
         return mHost.getSystemBarsAppearance();
     }
 
@@ -1544,6 +1556,10 @@
 
     @Override
     public @Behavior int getSystemBarsBehavior() {
+        if (!mHost.isSystemBarsBehaviorControlled()) {
+            // We only return the requested behavior, not the implied one.
+            return 0;
+        }
         return mHost.getSystemBarsBehavior();
     }
 
diff --git a/core/java/android/view/ViewRootInsetsControllerHost.java b/core/java/android/view/ViewRootInsetsControllerHost.java
index 27821fd..ce882da 100644
--- a/core/java/android/view/ViewRootInsetsControllerHost.java
+++ b/core/java/android/view/ViewRootInsetsControllerHost.java
@@ -180,14 +180,15 @@
 
     @Override
     public int getSystemBarsAppearance() {
-        if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) == 0) {
-            // We only return the requested appearance, not the implied one.
-            return 0;
-        }
         return mViewRoot.mWindowAttributes.insetsFlags.appearance;
     }
 
     @Override
+    public boolean isSystemBarsAppearanceControlled() {
+        return (mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_APPEARANCE_CONTROLLED) != 0;
+    }
+
+    @Override
     public void setSystemBarsBehavior(int behavior) {
         mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED;
         if (mViewRoot.mWindowAttributes.insetsFlags.behavior != behavior) {
@@ -199,14 +200,15 @@
 
     @Override
     public int getSystemBarsBehavior() {
-        if ((mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) == 0) {
-            // We only return the requested behavior, not the implied one.
-            return 0;
-        }
         return mViewRoot.mWindowAttributes.insetsFlags.behavior;
     }
 
     @Override
+    public boolean isSystemBarsBehaviorControlled() {
+        return (mViewRoot.mWindowAttributes.privateFlags & PRIVATE_FLAG_BEHAVIOR_CONTROLLED) != 0;
+    }
+
+    @Override
     public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) {
 
          // At the time we receive new leashes (e.g. InsetsSourceConsumer is processing
diff --git a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
index 848a5ba..d14adf6 100644
--- a/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
+++ b/core/java/com/android/internal/view/RecyclerViewCaptureHelper.java
@@ -16,11 +16,6 @@
 
 package com.android.internal.view;
 
-import static com.android.internal.view.ScrollCaptureViewSupport.computeScrollAmount;
-import static com.android.internal.view.ScrollCaptureViewSupport.findScrollingReferenceView;
-import static com.android.internal.view.ScrollCaptureViewSupport.transformFromContainerToRequest;
-import static com.android.internal.view.ScrollCaptureViewSupport.transformFromRequestToContainer;
-
 import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.util.Log;
@@ -43,6 +38,7 @@
  */
 public class RecyclerViewCaptureHelper implements ScrollCaptureViewHelper<ViewGroup> {
     private static final String TAG = "RVCaptureHelper";
+
     private int mScrollDelta;
     private boolean mScrollBarWasEnabled;
     private int mOverScrollMode;
@@ -66,7 +62,6 @@
         result.scrollDelta = mScrollDelta;
         result.availableArea = new Rect(); // empty
 
-        Log.d(TAG, "current scrollDelta: " + mScrollDelta);
         if (!recyclerView.isVisibleToUser() || recyclerView.getChildCount() == 0) {
             Log.w(TAG, "recyclerView is empty or not visible, cannot continue");
             return result; // result.availableArea == empty Rect
@@ -76,22 +71,18 @@
         Rect requestedContainerBounds = new Rect(requestRect);
         requestedContainerBounds.offset(0, -mScrollDelta);
         requestedContainerBounds.offset(scrollBounds.left, scrollBounds.top);
-
         // requestedContainerBounds is now in recyclerview-local coordinates
-        Log.d(TAG, "requestedContainerBounds: " + requestedContainerBounds);
 
         // Save a copy for later
         View anchor = findChildNearestTarget(recyclerView, requestedContainerBounds);
         if (anchor == null) {
-            Log.d(TAG, "Failed to locate anchor view");
-            return result; // result.availableArea == null
+            Log.w(TAG, "Failed to locate anchor view");
+            return result; // result.availableArea == empty rect
         }
 
-        Log.d(TAG, "Anchor view:" + anchor);
         Rect requestedContentBounds = new Rect(requestedContainerBounds);
         recyclerView.offsetRectIntoDescendantCoords(anchor, requestedContentBounds);
 
-        Log.d(TAG, "requestedContentBounds = " + requestedContentBounds);
         int prevAnchorTop = anchor.getTop();
         // Note: requestChildRectangleOnScreen may modify rectangle, must pass pass in a copy here
         Rect input = new Rect(requestedContentBounds);
@@ -101,34 +92,27 @@
         if (remainingHeight > 0) {
             input.inset(0, -remainingHeight / 2);
         }
-        Log.d(TAG, "input (post center adjustment) = " + input);
 
         if (recyclerView.requestChildRectangleOnScreen(anchor, input, true)) {
             int scrolled = prevAnchorTop - anchor.getTop(); // inverse of movement
-            Log.d(TAG, "RecyclerView scrolled by " + scrolled + " px");
             mScrollDelta += scrolled; // view.top-- is equivalent to parent.scrollY++
             result.scrollDelta = mScrollDelta;
-            Log.d(TAG, "requestedContentBounds, (post-request-rect) = " + requestedContentBounds);
         }
 
         requestedContainerBounds.set(requestedContentBounds);
         recyclerView.offsetDescendantRectToMyCoords(anchor, requestedContainerBounds);
-        Log.d(TAG, "requestedContainerBounds, (post-scroll): " + requestedContainerBounds);
 
         Rect recyclerLocalVisible = new Rect(scrollBounds);
         recyclerView.getLocalVisibleRect(recyclerLocalVisible);
-        Log.d(TAG, "recyclerLocalVisible: " + recyclerLocalVisible);
 
         if (!requestedContainerBounds.intersect(recyclerLocalVisible)) {
             // Requested area is still not visible
-            Log.d(TAG, "requested bounds not visible!");
             return result;
         }
         Rect available = new Rect(requestedContainerBounds);
         available.offset(-scrollBounds.left, -scrollBounds.top);
         available.offset(0, mScrollDelta);
         result.availableArea = available;
-        Log.d(TAG, "availableArea: " + result.availableArea);
         return result;
     }
 
@@ -154,22 +138,17 @@
 
         Rect parentLocalVis = new Rect();
         parent.getLocalVisibleRect(parentLocalVis);
-        Log.d(TAG, "findChildNearestTarget: parentVis=" + parentLocalVis
-                + " targetRect=" + targetRect);
 
         Rect frame = new Rect();
         for (int i = 0; i < parent.getChildCount(); i++) {
             final View child = parent.getChildAt(i);
             child.getHitRect(frame);
-            Log.d(TAG, "child #" + i + " hitRect=" + frame);
 
             if (child.getVisibility() != View.VISIBLE) {
-                Log.d(TAG, "child #" + i + " is not visible");
                 continue;
             }
 
             int centerDistance = Math.abs(targetRect.centerY() - frame.centerY());
-            Log.d(TAG, "child #" + i + " : center to center: " + centerDistance + "px");
 
             if (centerDistance < minCenterDistance) {
                 // closer to center
diff --git a/core/java/com/android/internal/view/ScrollCaptureInternal.java b/core/java/com/android/internal/view/ScrollCaptureInternal.java
index ffee16a..e3a9fda 100644
--- a/core/java/com/android/internal/view/ScrollCaptureInternal.java
+++ b/core/java/com/android/internal/view/ScrollCaptureInternal.java
@@ -34,7 +34,7 @@
     private static final String TAG = "ScrollCaptureInternal";
 
     // Log found scrolling views
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     // Log all investigated views, as well as heuristic checks
     private static final boolean DEBUG_VERBOSE = false;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 96d5a92..9802f8d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4739,9 +4739,15 @@
          MediaSessionService. -->
     <string name="config_customMediaSessionPolicyProvider"></string>
 
+    <!-- The min scale for the wallpaper when it's zoomed out -->
+    <item name="config_wallpaperMinScale" format="float" type="dimen">1</item>
+
     <!-- The max scale for the wallpaper when it's zoomed in -->
     <item name="config_wallpaperMaxScale" format="float" type="dimen">1.10</item>
 
+    <!-- If true, the wallpaper will scale regardless of the value of shouldZoomOutWallpaper() -->
+    <bool name="config_alwaysScaleWallpaper">false</bool>
+
     <!-- Package name that will receive an explicit manifest broadcast for
          android.os.action.POWER_SAVE_MODE_CHANGED. -->
     <string name="config_powerSaveModeChangedListenerPackage" translatable="false"></string>
@@ -4760,6 +4766,16 @@
     <!-- pdp data reject retry delay in ms -->
     <integer name="config_pdp_reject_retry_delay_ms">-1</integer>
 
+    <!-- Duration in milliseconds for device to vibrate on mash press on power
+         button. -->
+    <integer name="config_mashPressVibrateTimeOnPowerButton">0</integer>
+
+    <!-- Control the behavior when the user presses the power button 5 times.
+           0 - Nothing
+           1 - Launch panic button gesture
+    -->
+    <integer name="config_mashPressOnPowerBehavior">0</integer>
+
     <!-- Whether or not to enable the binder heavy hitter watcher by default -->
     <bool name="config_defaultBinderHeavyHitterWatcherEnabled">false</bool>
 
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index c208350..e1035f3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -314,6 +314,7 @@
         <permission name="android.permission.INSTALL_DYNAMIC_SYSTEM"/>
         <permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
         <permission name="android.permission.INSTALL_PACKAGES"/>
+        <permission name="android.permission.INSTALL_PACKAGE_UPDATES"/>
         <!-- Needed for test only -->
         <permission name="android.permission.ACCESS_MTP"/>
         <!-- Needed for test only -->
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 54b81ad..2038cff 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
@@ -140,8 +140,6 @@
 
     private final ContentObserver mActivatedObserver;
     private final ContentObserver mEnabledObserver;
-    private final ContentObserver mTimeoutObserver;
-    private final ContentObserver mTaskChangeExitObserver;
     private final ContentObserver mSwipeToNotificationEnabledObserver;
     private final ContentObserver mShortcutEnabledObserver;
 
@@ -225,7 +223,7 @@
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
         OneHandedState transitionState = new OneHandedState();
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
-                displayLayout, windowManager, settingsUtil, mainExecutor);
+                windowManager);
         OneHandedAnimationController animationController =
                 new OneHandedAnimationController(context);
         OneHandedTouchHandler touchHandler = new OneHandedTouchHandler(timeoutHandler,
@@ -292,8 +290,6 @@
 
         mActivatedObserver = getObserver(this::onActivatedActionChanged);
         mEnabledObserver = getObserver(this::onEnabledSettingChanged);
-        mTimeoutObserver = getObserver(this::onTimeoutSettingChanged);
-        mTaskChangeExitObserver = getObserver(this::onTaskChangeExitSettingChanged);
         mSwipeToNotificationEnabledObserver =
                 getObserver(this::onSwipeToNotificationEnabledChanged);
         mShortcutEnabledObserver = getObserver(this::onShortcutEnabledChanged);
@@ -440,10 +436,6 @@
                 mContext.getContentResolver(), mActivatedObserver, newUserId);
         mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED,
                 mContext.getContentResolver(), mEnabledObserver, newUserId);
-        mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT,
-                mContext.getContentResolver(), mTimeoutObserver, newUserId);
-        mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.TAPS_APP_TO_EXIT,
-                mContext.getContentResolver(), mTaskChangeExitObserver, newUserId);
         mOneHandedSettingsUtil.registerSettingsKeyObserver(
                 Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED,
                 mContext.getContentResolver(), mSwipeToNotificationEnabledObserver, newUserId);
@@ -456,10 +448,6 @@
         mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
                 mEnabledObserver);
         mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
-                mTimeoutObserver);
-        mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
-                mTaskChangeExitObserver);
-        mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
                 mSwipeToNotificationEnabledObserver);
         mOneHandedSettingsUtil.unregisterSettingsKeyObserver(mContext.getContentResolver(),
                 mShortcutEnabledObserver);
@@ -474,6 +462,7 @@
                 .getSettingsTapsAppToExit(mContext.getContentResolver(), mUserId));
         setSwipeToNotificationEnabled(mOneHandedSettingsUtil
                 .getSettingsSwipeToNotificationEnabled(mContext.getContentResolver(), mUserId));
+        onShortcutEnabledChanged();
     }
 
     private void updateDisplayLayout(int displayId) {
@@ -547,46 +536,6 @@
     }
 
     @VisibleForTesting
-    void onTimeoutSettingChanged() {
-        final int newTimeout = mOneHandedSettingsUtil.getSettingsOneHandedModeTimeout(
-                mContext.getContentResolver(), mUserId);
-        int metricsId = OneHandedUiEventLogger.OneHandedSettingsTogglesEvent.INVALID.getId();
-        switch (newTimeout) {
-            case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_NEVER:
-                metricsId = OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_NEVER;
-                break;
-            case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_SHORT_IN_SECONDS:
-                metricsId = OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_4;
-                break;
-            case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS:
-                metricsId = OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_8;
-                break;
-            case OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_LONG_IN_SECONDS:
-                metricsId = OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_12;
-                break;
-            default:
-                // do nothing
-                break;
-        }
-        mOneHandedUiEventLogger.writeEvent(metricsId);
-
-        if (mTimeoutHandler != null) {
-            mTimeoutHandler.setTimeout(newTimeout);
-        }
-    }
-
-    @VisibleForTesting
-    void onTaskChangeExitSettingChanged() {
-        final boolean enabled = mOneHandedSettingsUtil.getSettingsTapsAppToExit(
-                mContext.getContentResolver(), mUserId);
-        mOneHandedUiEventLogger.writeEvent(enabled
-                ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_ON
-                : OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_APP_TAPS_EXIT_OFF);
-
-        setTaskChangeToExit(enabled);
-    }
-
-    @VisibleForTesting
     void onSwipeToNotificationEnabledChanged() {
         final boolean enabled =
                 mOneHandedSettingsUtil.getSettingsSwipeToNotificationEnabled(
@@ -601,6 +550,10 @@
     void onShortcutEnabledChanged() {
         mIsShortcutEnabled = mOneHandedSettingsUtil.getShortcutEnabled(
                 mContext.getContentResolver(), mUserId);
+
+        mOneHandedUiEventLogger.writeEvent(mIsShortcutEnabled
+                ? OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_ON
+                : OneHandedUiEventLogger.EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_OFF);
     }
 
     private void setupTimeoutListener() {
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 6cee404..0f6c4b0 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
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.onehanded;
 
-import static android.os.UserHandle.myUserId;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
@@ -24,7 +24,6 @@
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
 
 import android.annotation.Nullable;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
@@ -41,7 +40,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.ShellExecutor;
 
 import java.io.PrintWriter;
 
@@ -56,57 +54,44 @@
     private static final String TAG = "OneHandedTutorialHandler";
     private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
             "persist.debug.one_handed_offset_percentage";
-    private static final int MAX_TUTORIAL_SHOW_COUNT = 2;
 
     private final float mTutorialHeightRatio;
     private final WindowManager mWindowManager;
-    private final OneHandedSettingsUtil mSettingsUtil;
-    private final ShellExecutor mShellExecutor;
 
-    private boolean mCanShow;
+    private boolean mIsShowing;
     private @OneHandedState.State int mCurrentState;
-    private int mShownCounts;
     private int mTutorialAreaHeight;
 
     private Context mContext;
-    private ContentResolver mContentResolver;
     private Rect mDisplayBounds;
     private @Nullable View mTutorialView;
     private @Nullable ViewGroup mTargetViewContainer;
 
-    private final OneHandedAnimationCallback mAnimationCallback = new OneHandedAnimationCallback() {
-        @Override
-        public void onAnimationUpdate(float xPos, float yPos) {
-            if (!canShowTutorial()) {
-                return;
-            }
-            mTargetViewContainer.setTransitionGroup(true);
-            mTargetViewContainer.setTranslationY(yPos - mTargetViewContainer.getHeight());
-        }
-    };
+    private final OneHandedAnimationCallback mAnimationCallback;
 
-    public OneHandedTutorialHandler(Context context, DisplayLayout displayLayout,
-            WindowManager windowManager, OneHandedSettingsUtil settingsUtil,
-            ShellExecutor mainExecutor) {
+    public OneHandedTutorialHandler(Context context, WindowManager windowManager) {
         mContext = context;
-        mContentResolver = context.getContentResolver();
         mWindowManager = windowManager;
-        mSettingsUtil = settingsUtil;
-        mShellExecutor = mainExecutor;
         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));
         mTutorialHeightRatio = sysPropPercentageConfig / 100.0f;
-        mShownCounts = mSettingsUtil.getTutorialShownCounts(mContentResolver, myUserId());
+        mAnimationCallback = new OneHandedAnimationCallback() {
+            @Override
+            public void onAnimationUpdate(float xPos, float yPos) {
+                if (!isShowing()) {
+                    return;
+                }
+                mTargetViewContainer.setTransitionGroup(true);
+                mTargetViewContainer.setTranslationY(yPos - mTargetViewContainer.getHeight());
+            }
+        };
     }
 
     @Override
     public void onStateChanged(int newState) {
         mCurrentState = newState;
-        if (!canShowTutorial()) {
-            return;
-        }
         switch (newState) {
             case STATE_ENTERING:
                 createViewAndAttachToWindow(mContext);
@@ -139,7 +124,7 @@
 
     @VisibleForTesting
     void createViewAndAttachToWindow(Context context) {
-        if (!canShowTutorial()) {
+        if (isShowing()) {
             return;
         }
         mTutorialView = LayoutInflater.from(context).inflate(R.layout.one_handed_tutorial, null);
@@ -150,15 +135,6 @@
         attachTargetToWindow();
     }
 
-    @VisibleForTesting
-    boolean setTutorialShownCountIncrement() {
-        if (!canShowTutorial()) {
-            return false;
-        }
-        mShownCounts += 1;
-        return mSettingsUtil.setTutorialShownCounts(mContentResolver, mShownCounts, myUserId());
-    }
-
     /**
      * Adds the tutorial target view to the WindowManager and update its layout.
      */
@@ -166,6 +142,7 @@
         if (!mTargetViewContainer.isAttachedToWindow()) {
             try {
                 mWindowManager.addView(mTargetViewContainer, getTutorialTargetLayoutParams());
+                mIsShowing = true;
             } catch (IllegalStateException e) {
                 // This shouldn't happen, but if the target is already added, just update its
                 // layout params.
@@ -179,14 +156,12 @@
     void removeTutorialFromWindowManager(boolean increment) {
         if (mTargetViewContainer != null && mTargetViewContainer.isAttachedToWindow()) {
             mWindowManager.removeViewImmediate(mTargetViewContainer);
-            if (increment) {
-                setTutorialShownCountIncrement();
-            }
+            mIsShowing = false;
         }
     }
 
     @Nullable OneHandedAnimationCallback getAnimationCallback() {
-        return canShowTutorial() ? mAnimationCallback : null /* Disabled */;
+        return isShowing() ? mAnimationCallback : null /* Disabled */;
     }
 
     /**
@@ -200,6 +175,7 @@
                     | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                     PixelFormat.TRANSLUCENT);
         lp.gravity = Gravity.TOP | Gravity.LEFT;
+        lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setFitInsetsTypes(0 /* types */);
         lp.setTitle("one-handed-tutorial-overlay");
@@ -207,17 +183,14 @@
     }
 
     @VisibleForTesting
-    boolean canShowTutorial() {
-        return mCanShow = mShownCounts < MAX_TUTORIAL_SHOW_COUNT;
+    boolean isShowing() {
+        return mIsShowing;
     }
 
     /**
      * onConfigurationChanged events for updating tutorial text.
      */
     public void onConfigurationChanged() {
-        if (!canShowTutorial()) {
-            return;
-        }
         removeTutorialFromWindowManager(false /* increment */);
         if (mCurrentState == STATE_ENTERING || mCurrentState == STATE_ACTIVE) {
             createViewAndAttachToWindow(mContext);
@@ -227,14 +200,12 @@
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
         pw.println(TAG);
-        pw.print(innerPrefix + "mCanShow=");
-        pw.println(mCanShow);
+        pw.print(innerPrefix + "mIsShowing=");
+        pw.println(mIsShowing);
         pw.print(innerPrefix + "mCurrentState=");
         pw.println(mCurrentState);
         pw.print(innerPrefix + "mDisplayBounds=");
         pw.println(mDisplayBounds);
-        pw.print(innerPrefix + "mShownCounts=");
-        pw.println(mShownCounts);
         pw.print(innerPrefix + "mTutorialAreaHeight=");
         pw.println(mTutorialAreaHeight);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedUiEventLogger.java
index 4e610fa..1cf4080 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedUiEventLogger.java
@@ -52,6 +52,8 @@
     public static final int EVENT_ONE_HANDED_SETTINGS_TIMEOUT_SECONDS_12 = 17;
     public static final int EVENT_ONE_HANDED_SETTINGS_SHOW_NOTIFICATION_ENABLED_ON = 18;
     public static final int EVENT_ONE_HANDED_SETTINGS_SHOW_NOTIFICATION_ENABLED_OFF = 19;
+    public static final int EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_ON = 20;
+    public static final int EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_OFF = 21;
 
     private static final String[] EVENT_TAGS = {
             "one_handed_trigger_gesture_in",
@@ -73,7 +75,9 @@
             "one_handed_settings_timeout_seconds_8",
             "one_handed_settings_timeout_seconds_12",
             "one_handed_settings_show_notification_enabled_on",
-            "one_handed_settings_show_notification_enabled_off"
+            "one_handed_settings_show_notification_enabled_off",
+            "one_handed_settings_shortcut_enabled_on",
+            "one_handed_settings_shortcut_enabled_off"
     };
 
     public OneHandedUiEventLogger(UiEventLogger uiEventLogger) {
@@ -162,7 +166,13 @@
         ONE_HANDED_SETTINGS_TOGGLES_SHOW_NOTIFICATION_ENABLED_ON(847),
 
         @UiEvent(doc = "One-Handed mode show notification toggle off")
-        ONE_HANDED_SETTINGS_TOGGLES_SHOW_NOTIFICATION_ENABLED_OFF(848);
+        ONE_HANDED_SETTINGS_TOGGLES_SHOW_NOTIFICATION_ENABLED_OFF(848),
+
+        @UiEvent(doc = "One-Handed mode shortcut toggle on")
+        ONE_HANDED_SETTINGS_TOGGLES_SHORTCUT_ENABLED_ON(870),
+
+        @UiEvent(doc = "One-Handed mode shortcut toggle off")
+        ONE_HANDED_SETTINGS_TOGGLES_SHORTCUT_ENABLED_OFF(871);
 
         private final int mId;
 
@@ -265,6 +275,14 @@
                 mUiEventLogger.log(OneHandedSettingsTogglesEvent
                         .ONE_HANDED_SETTINGS_TOGGLES_SHOW_NOTIFICATION_ENABLED_OFF);
                 break;
+            case EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_ON:
+                mUiEventLogger.log(OneHandedSettingsTogglesEvent
+                        .ONE_HANDED_SETTINGS_TOGGLES_SHORTCUT_ENABLED_ON);
+                break;
+            case EVENT_ONE_HANDED_SETTINGS_SHORTCUT_ENABLED_OFF:
+                mUiEventLogger.log(OneHandedSettingsTogglesEvent
+                        .ONE_HANDED_SETTINGS_TOGGLES_SHORTCUT_ENABLED_OFF);
+                break;
             default:
                 // Do nothing
                 break;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 9ec7d30..b224ae6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -238,13 +238,6 @@
     }
 
     @Test
-    public void testSettingsObserverUpdateTimeout() {
-        mSpiedOneHandedController.onTimeoutSettingChanged();
-
-        verify(mSpiedTimeoutHandler, atLeastOnce()).setTimeout(anyInt());
-    }
-
-    @Test
     public void testSettingsObserverUpdateSwipeToNotification() {
         mSpiedOneHandedController.onSwipeToNotificationEnabledChanged();
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
index 1bc2a08..25bdb8e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java
@@ -19,8 +19,6 @@
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
@@ -68,25 +66,18 @@
         mDisplayLayout = new DisplayLayout(mContext, mDisplay);
         mSpiedTransitionState = spy(new OneHandedState());
         mSpiedTutorialHandler = spy(
-                new OneHandedTutorialHandler(mContext, mDisplayLayout, mMockWindowManager,
-                        mMockSettingsUtil, mMockShellMainExecutor));
+                new OneHandedTutorialHandler(mContext, mMockWindowManager));
         mTimeoutHandler = new OneHandedTimeoutHandler(mMockShellMainExecutor);
     }
 
     @Test
-    public void testDefaultZeroShownCounts_canShowTutorial() {
-        assertThat(mSpiedTutorialHandler.canShowTutorial()).isTrue();
-        verify(mMockShellMainExecutor, never()).execute(any());
-    }
-
-    @Test
     public void testDefaultZeroShownCounts_doNotAttachWindow() {
         verify(mMockShellMainExecutor, never()).execute(any());
     }
 
     @Test
     public void testOnStateChangedEntering_createViewAndAttachToWindow() {
-        when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true);
+        when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
         try {
             mSpiedTutorialHandler.onStateChanged(STATE_ENTERING);
         } catch (ClassCastException e) {
@@ -98,7 +89,7 @@
 
     @Test
     public void testOnStateChangedNone_removeViewAndAttachToWindow() {
-        when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true);
+        when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
         try {
             mSpiedTutorialHandler.onStateChanged(STATE_NONE);
         } catch (ClassCastException e) {
@@ -110,19 +101,19 @@
 
     @Test
     public void testOnStateChangedNone_shouldNotAttachWindow() {
-        when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true);
+        when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
         try {
             mSpiedTutorialHandler.onStateChanged(STATE_NONE);
         } catch (ClassCastException e) {
             // no-op, just assert setTutorialShownCountIncrement() never be called
         }
 
-        verify(mSpiedTutorialHandler, never()).setTutorialShownCountIncrement();
+        verify(mSpiedTutorialHandler, never()).createViewAndAttachToWindow(any());
     }
 
     @Test
     public void testOnConfigurationChanged_shouldUpdateViewContent() {
-        when(mSpiedTutorialHandler.canShowTutorial()).thenReturn(true);
+        when(mSpiedTutorialHandler.isShowing()).thenReturn(true);
         try {
             mSpiedTutorialHandler.onStateChanged(STATE_ENTERING);
         } catch (ClassCastException e) {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 0a232d6..2c299fa 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -47,6 +47,7 @@
         "-DATRACE_TAG=ATRACE_TAG_VIEW",
         "-DLOG_TAG=\"OpenGLRenderer\"",
         "-Wall",
+        "-Wthread-safety",
         "-Wno-unused-parameter",
         "-Wunreachable-code",
         "-Werror",
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index dd977c3..34e5577 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -99,7 +99,7 @@
     mFrameIntervalLegacy = frameIntervalNanos;
 }
 
-void JankTracker::calculateLegacyJank(FrameInfo& frame) {
+void JankTracker::calculateLegacyJank(FrameInfo& frame) REQUIRES(mDataMutex) {
     // Fast-path for jank-free frames
     int64_t totalDuration = frame.duration(sFrameStart, FrameInfoIndex::SwapBuffersCompleted);
     if (mDequeueTimeForgivenessLegacy && frame[FrameInfoIndex::DequeueBufferDuration] > 500_us) {
@@ -257,7 +257,7 @@
     }
 }
 
-void JankTracker::recomputeThresholds(int64_t frameBudget) {
+void JankTracker::recomputeThresholds(int64_t frameBudget) REQUIRES(mDataMutex) {
     if (mThresholdsFrameBudget == frameBudget) {
         return;
     }
@@ -308,7 +308,7 @@
     dprintf(fd, "\n---PROFILEDATA---\n\n");
 }
 
-void JankTracker::reset() {
+void JankTracker::reset() REQUIRES(mDataMutex) {
     mFrames.clear();
     mData->reset();
     (*mGlobalData)->reset();
diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h
index 0d2574c..bdb784d 100644
--- a/libs/hwui/JankTracker.h
+++ b/libs/hwui/JankTracker.h
@@ -62,7 +62,7 @@
     // Calculates the 'legacy' jank information, i.e. with outdated refresh rate information and
     // without GPU completion or deadlined information.
     void calculateLegacyJank(FrameInfo& frame);
-    void dumpStats(int fd) { dumpData(fd, &mDescription, mData.get()); }
+    void dumpStats(int fd) NO_THREAD_SAFETY_ANALYSIS { dumpData(fd, &mDescription, mData.get()); }
     void dumpFrames(int fd);
     void reset();
 
diff --git a/libs/hwui/ProfileDataContainer.cpp b/libs/hwui/ProfileDataContainer.cpp
index 41afc0e..dd78847 100644
--- a/libs/hwui/ProfileDataContainer.cpp
+++ b/libs/hwui/ProfileDataContainer.cpp
@@ -27,7 +27,7 @@
 namespace android {
 namespace uirenderer {
 
-void ProfileDataContainer::freeData() {
+void ProfileDataContainer::freeData() REQUIRES(mJankDataMutex) {
     if (mIsMapped) {
         munmap(mData, sizeof(ProfileData));
     } else {
diff --git a/libs/hwui/ProfileDataContainer.h b/libs/hwui/ProfileDataContainer.h
index a61b8dc..7d1b46c 100644
--- a/libs/hwui/ProfileDataContainer.h
+++ b/libs/hwui/ProfileDataContainer.h
@@ -37,8 +37,9 @@
     void rotateStorage();
     void switchStorageToAshmem(int ashmemfd);
 
-    ProfileData* get() { return mData; }
-    ProfileData* operator->() { return mData; }
+    ProfileData* get() NO_THREAD_SAFETY_ANALYSIS { return mData; }
+
+    ProfileData* operator->() NO_THREAD_SAFETY_ANALYSIS { return mData; }
 
     std::mutex& getDataMutex() { return mJankDataMutex; }
 
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 76c4a03..7b962cc 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -187,7 +187,7 @@
 void SkiaRecordingCanvas::FilterForImage(SkPaint& paint) {
     // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
     // older.
-    if (sApiLevel <= 27 && paint.getBlendMode() == SkBlendMode::kClear) {
+    if (sApiLevel <= 27 && paint.asBlendMode() == SkBlendMode::kClear) {
         paint.setBlendMode(SkBlendMode::kDstOut);
     }
 }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 4dcd5af..025be7b 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -614,6 +614,7 @@
             mCurrentFrameInfo->markFrameCompleted();
             mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
                     = mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
+            std::scoped_lock lock(mFrameMetricsReporterMutex);
             mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter);
         }
     }
@@ -638,9 +639,12 @@
 }
 
 void CanvasContext::reportMetricsWithPresentTime() {
-    if (mFrameMetricsReporter == nullptr) {
-        return;
-    }
+    {  // acquire lock
+        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        if (mFrameMetricsReporter == nullptr) {
+            return;
+        }
+    }  // release lock
     if (mNativeSurface == nullptr) {
         return;
     }
@@ -666,7 +670,22 @@
             nullptr /*outReleaseTime*/);
 
     forthBehind->set(FrameInfoIndex::DisplayPresentTime) = presentTime;
-    mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/);
+    {  // acquire lock
+        std::scoped_lock lock(mFrameMetricsReporterMutex);
+        if (mFrameMetricsReporter != nullptr) {
+            mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/);
+        }
+    }  // release lock
+}
+
+FrameInfo* CanvasContext::getFrameInfoFromLast4(uint64_t frameNumber) {
+    std::scoped_lock lock(mLast4FrameInfosMutex);
+    for (size_t i = 0; i < mLast4FrameInfos.size(); i++) {
+        if (mLast4FrameInfos[i].second == frameNumber) {
+            return mLast4FrameInfos[i].first;
+        }
+    }
+    return nullptr;
 }
 
 void CanvasContext::onSurfaceStatsAvailable(void* context, ASurfaceControl* control,
@@ -680,22 +699,13 @@
     nsecs_t gpuCompleteTime = functions.getAcquireTimeFunc(stats);
     uint64_t frameNumber = functions.getFrameNumberFunc(stats);
 
-    FrameInfo* frameInfo = nullptr;
-    {
-        std::lock_guard(instance->mLast4FrameInfosMutex);
-        for (size_t i = 0; i < instance->mLast4FrameInfos.size(); i++) {
-            if (instance->mLast4FrameInfos[i].second == frameNumber) {
-                frameInfo = instance->mLast4FrameInfos[i].first;
-                break;
-            }
-        }
-    }
+    FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber);
 
     if (frameInfo != nullptr) {
         frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
                 frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
         frameInfo->set(FrameInfoIndex::GpuCompleted) = gpuCompleteTime;
-        std::lock_guard(instance->mFrameMetricsReporterMutex);
+        std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
         instance->mJankTracker.finishFrame(*frameInfo, instance->mFrameMetricsReporter);
     }
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 85af3e4..6dbfcc3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -160,6 +160,7 @@
     void setContentDrawBounds(const Rect& bounds) { mContentDrawBounds = bounds; }
 
     void addFrameMetricsObserver(FrameMetricsObserver* observer) {
+        std::scoped_lock lock(mFrameMetricsReporterMutex);
         if (mFrameMetricsReporter.get() == nullptr) {
             mFrameMetricsReporter.reset(new FrameMetricsReporter());
         }
@@ -168,10 +169,10 @@
     }
 
     void removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+        std::scoped_lock lock(mFrameMetricsReporterMutex);
         if (mFrameMetricsReporter.get() != nullptr) {
             mFrameMetricsReporter->removeObserver(observer);
             if (!mFrameMetricsReporter->hasObservers()) {
-                std::lock_guard lock(mFrameMetricsReporterMutex);
                 mFrameMetricsReporter.reset(nullptr);
             }
         }
@@ -245,6 +246,8 @@
      */
     void reportMetricsWithPresentTime();
 
+    FrameInfo* getFrameInfoFromLast4(uint64_t frameNumber);
+
     // The same type as Frame.mWidth and Frame.mHeight
     int32_t mLastFrameWidth = 0;
     int32_t mLastFrameHeight = 0;
@@ -305,7 +308,8 @@
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
-    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter;
+    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
+            GUARDED_BY(mFrameMetricsReporterMutex);
     std::mutex mFrameMetricsReporterMutex;
 
     std::set<RenderNode*> mPrefetchedLayers;
diff --git a/libs/hwui/utils/PaintUtils.h b/libs/hwui/utils/PaintUtils.h
index a8f2d9a..f0fb068 100644
--- a/libs/hwui/utils/PaintUtils.h
+++ b/libs/hwui/utils/PaintUtils.h
@@ -48,7 +48,7 @@
         }
 
         // Only let simple srcOver / src blending modes declare opaque, since behavior is clear.
-        SkBlendMode mode = paint->getBlendMode();
+        const auto mode = paint->asBlendMode();
         return mode == SkBlendMode::kSrcOver || mode == SkBlendMode::kSrc;
     }
 
@@ -59,7 +59,7 @@
     }
 
     static inline SkBlendMode getBlendModeDirect(const SkPaint* paint) {
-        return paint ? paint->getBlendMode() : SkBlendMode::kSrcOver;
+        return paint ? paint->getBlendMode_or(SkBlendMode::kSrcOver) : SkBlendMode::kSrcOver;
     }
 
     static inline int getAlphaDirect(const SkPaint* paint) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e40bd57..7c82a9b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -73,6 +73,7 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -103,7 +104,7 @@
     private static final AudioVolumeGroupChangeHandler sAudioAudioVolumeGroupChangedHandler =
             new AudioVolumeGroupChangeHandler();
 
-    private static Context sContext;
+    private static WeakReference<Context> sContext;
 
     /**
      * Broadcast intent, a hint for applications that audio is about to become
@@ -812,7 +813,7 @@
         } else {
             mOriginalContext = context;
         }
-        sContext = context;
+        sContext = new WeakReference<>(context);
     }
 
     @UnsupportedAppUsage
@@ -7273,23 +7274,27 @@
      */
     public static boolean hasHapticChannels(@Nullable Context context, @NonNull Uri uri) {
         Objects.requireNonNull(uri);
+
         if (context != null) {
             return hasHapticChannelsImpl(context, uri);
-        } else if (sContext != null) {
+        }
+
+        Context cachedContext = sContext.get();
+        if (cachedContext != null) {
             if (DEBUG) {
                 Log.d(TAG, "Try to use static context to query if having haptic channels");
             }
-            return hasHapticChannelsImpl(sContext, uri);
-        } else {
-            // Try with audio service context, this may fail to get correct result.
-            if (DEBUG) {
-                Log.d(TAG, "Try to use audio service context to query if having haptic channels");
-            }
-            try {
-                return getService().hasHapticChannels(uri);
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
+            return hasHapticChannelsImpl(cachedContext, uri);
+        }
+
+        // Try with audio service context, this may fail to get correct result.
+        if (DEBUG) {
+            Log.d(TAG, "Try to use audio service context to query if having haptic channels");
+        }
+        try {
+            return getService().hasHapticChannels(uri);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
     }
 
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 657c9ef..3cf9b03 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -339,6 +339,12 @@
     if (pC2Buffer != NULL) {
         pC2Buffer->unregisterOnDestroyNotify(&DestroyCallback, this);
     }
+
+    if (mLinearBlockObj != NULL) {
+        env->DeleteWeakGlobalRef(mLinearBlockObj);
+        mLinearBlockObj = NULL;
+    }
+
     mFilterClient = NULL;
 }
 
@@ -2450,7 +2456,10 @@
     if (old != NULL) {
         old->decStrong(thiz);
     }
-    env->SetLongField(thiz, gFields.tunerContext, (jlong)tuner.get());
+
+    if (tuner != NULL) {
+        env->SetLongField(thiz, gFields.tunerContext, (jlong)tuner.get());
+    }
 
     return old;
 }
@@ -4042,6 +4051,7 @@
 
 static jint android_media_tv_Tuner_close_tuner(JNIEnv* env, jobject thiz) {
     sp<JTuner> tuner = getTuner(env, thiz);
+    setTuner(env, thiz, NULL);
     return (jint) tuner->close();
 }
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bc1d420..1581e24 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -161,6 +161,7 @@
     <uses-permission android:name="android.permission.READ_INPUT_STATE" />
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.INSTALL_PACKAGE_UPDATES" />
     <uses-permission android:name="com.android.permission.USE_INSTALLER_V2" />
     <uses-permission android:name="android.permission.INSTALL_TEST_ONLY_PACKAGE" />
     <uses-permission android:name="com.android.permission.USE_SYSTEM_DATA_LOADERS" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3dd2f241..07ae535 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2956,6 +2956,8 @@
     <string name="new_notification_image_content_description"><xliff:g id="name" example="Anna">%1$s</xliff:g> sent an image</string>
     <!-- Content description text on the Conversation widget when a person has a new status posted [CHAR LIMIT=150] -->
     <string name="new_status_content_description"><xliff:g id="name" example="Anna">%1$s</xliff:g> has a status update: <xliff:g id="status" example="Listening to music">%2$s</xliff:g></string>
+    <!-- Content description text on the Conversation widget when a person is  available, meaning online on an application [CHAR LIMIT=150] -->
+    <string name="person_available">Available</string>
 
     <!-- Title to display in a notification when ACTION_BATTERY_CHANGED.EXTRA_PRESENT field is false
     [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 37b0625..71e2bb6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -48,10 +48,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.Display;
 import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
 import android.view.WindowManager;
 
 import com.android.internal.R;
@@ -72,6 +69,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import kotlin.Unit;
+
 /**
  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
  * appropriate biometric UI (e.g. BiometricDialogView).
@@ -107,7 +106,8 @@
     TaskStackListener mTaskStackListener;
     @VisibleForTesting
     IBiometricSysuiReceiver mReceiver;
-    @NonNull private final BiometricOrientationEventListener mOrientationListener;
+    @VisibleForTesting
+    @NonNull final BiometricOrientationEventListener mOrientationListener;
     @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
     @Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
     @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@@ -120,46 +120,6 @@
         }
     }
 
-    private class BiometricOrientationEventListener extends OrientationEventListener {
-        @Surface.Rotation private int mLastRotation = ORIENTATION_UNKNOWN;
-
-        BiometricOrientationEventListener(Context context) {
-            super(context);
-
-            final Display display = context.getDisplay();
-            if (display != null) {
-                mLastRotation = display.getRotation();
-            }
-        }
-
-        @Override
-        public void onOrientationChanged(int orientation) {
-            if (orientation == ORIENTATION_UNKNOWN) {
-                return;
-            }
-
-            final Display display = mContext.getDisplay();
-            if (display == null) {
-                return;
-            }
-
-            final int rotation = display.getRotation();
-            if (mLastRotation != rotation) {
-                mLastRotation = rotation;
-
-                if (mCurrentDialog != null) {
-                    mCurrentDialog.onOrientationChanged();
-                }
-                if (mUdfpsController != null) {
-                    mUdfpsController.onOrientationChanged();
-                }
-                if (mSidefpsController != null) {
-                    mSidefpsController.onOrientationChanged();
-                }
-            }
-        }
-    }
-
     @NonNull
     private final IFingerprintAuthenticatorsRegisteredCallback
             mFingerprintAuthenticatorsRegisteredCallback =
@@ -204,6 +164,7 @@
                 Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received");
                 mCurrentDialog.dismissWithoutCallback(true /* animate */);
                 mCurrentDialog = null;
+                mOrientationListener.disable();
 
                 try {
                     if (mReceiver != null) {
@@ -232,6 +193,7 @@
                         Log.w(TAG, "Evicting client due to: " + topPackage);
                         mCurrentDialog.dismissWithoutCallback(true /* animate */);
                         mCurrentDialog = null;
+                        mOrientationListener.disable();
 
                         if (mReceiver != null) {
                             mReceiver.onDialogDismissed(
@@ -470,8 +432,10 @@
         mUdfpsControllerFactory = udfpsControllerFactory;
         mSidefpsControllerFactory = sidefpsControllerFactory;
         mWindowManager = windowManager;
-        mOrientationListener = new BiometricOrientationEventListener(context);
-        mOrientationListener.enable();
+        mOrientationListener = new BiometricOrientationEventListener(context, () -> {
+            onOrientationChanged();
+            return Unit.INSTANCE;
+        });
 
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
 
@@ -666,6 +630,7 @@
         // BiometricService will have already sent the callback to the client in this case.
         // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
         mCurrentDialog = null;
+        mOrientationListener.disable();
     }
 
     /**
@@ -748,6 +713,7 @@
         mReceiver = (IBiometricSysuiReceiver) args.arg2;
         mCurrentDialog = newDialog;
         mCurrentDialog.show(mWindowManager, savedState);
+        mOrientationListener.enable();
     }
 
     private void onDialogDismissed(@DismissedReason int reason) {
@@ -757,6 +723,7 @@
         }
         mReceiver = null;
         mCurrentDialog = null;
+        mOrientationListener.disable();
     }
 
     @Override
@@ -769,6 +736,7 @@
             mCurrentDialog.onSaveState(savedState);
             mCurrentDialog.dismissWithoutCallback(false /* animate */);
             mCurrentDialog = null;
+            mOrientationListener.disable();
 
             // Only show the dialog if necessary. If it was animating out, the dialog is supposed
             // to send its pending callback immediately.
@@ -789,6 +757,12 @@
         }
     }
 
+    private void onOrientationChanged() {
+        if (mCurrentDialog != null) {
+            mCurrentDialog.onOrientationChanged();
+        }
+    }
+
     protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
             int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
             boolean skipIntro, long operationId,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt
new file mode 100644
index 0000000..08ea857
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricOrientationEventListener.kt
@@ -0,0 +1,60 @@
+/*
+ * 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.biometrics
+
+import android.content.Context
+import android.view.OrientationEventListener
+
+/**
+ * An [OrientationEventListener] that invokes the [onOrientationChanged] callback whenever
+ * the orientation of the device has changed in order to keep overlays for biometric sensors
+ * aligned with the device's screen.
+ */
+class BiometricOrientationEventListener(
+    private val context: Context,
+    private val onOrientationChanged: () -> Unit
+) : OrientationEventListener(context) {
+
+    /** If actively listening (not available in base class). */
+    var enabled: Boolean = false
+        private set
+
+    private var lastRotation = context.display?.rotation ?: ORIENTATION_UNKNOWN
+
+    override fun onOrientationChanged(orientation: Int) {
+        if (orientation == ORIENTATION_UNKNOWN) {
+            return
+        }
+
+        val rotation = context.display?.rotation ?: return
+        if (lastRotation != rotation) {
+            lastRotation = rotation
+
+            onOrientationChanged()
+        }
+    }
+
+    override fun enable() {
+        enabled = true
+        super.enable()
+    }
+
+    override fun disable() {
+        enabled = false
+        super.disable()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
index 436e1e4..a51c2b8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.java
@@ -41,6 +41,8 @@
 
 import javax.inject.Inject;
 
+import kotlin.Unit;
+
 /**
  * Shows and hides the side fingerprint sensor (side-fps) overlay and handles side fps touch events.
  */
@@ -52,6 +54,8 @@
     private final FingerprintManager mFingerprintManager;
     private final WindowManager mWindowManager;
     private final DelayableExecutor mFgExecutor;
+    @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
+
     // TODO: update mDisplayHeight and mDisplayWidth for multi-display devices
     private final int mDisplayHeight;
     private final int mDisplayWidth;
@@ -95,6 +99,10 @@
         mFingerprintManager = checkNotNull(fingerprintManager);
         mWindowManager = windowManager;
         mFgExecutor = fgExecutor;
+        mOrientationListener = new BiometricOrientationEventListener(context, () -> {
+            onOrientationChanged();
+            return Unit.INSTANCE;
+        });
 
         mSensorProps = findFirstSidefps();
         checkArgument(mSensorProps != null);
@@ -119,14 +127,15 @@
         mFingerprintManager.setSidefpsController(mSidefpsControllerImpl);
     }
 
-    void show() {
+    private void show() {
         mView = (SidefpsView) mInflater.inflate(R.layout.sidefps_view, null, false);
         mView.setSensorProperties(mSensorProps);
         mWindowManager.addView(mView, computeLayoutParams());
 
+        mOrientationListener.enable();
     }
 
-    void hide() {
+    private void hide() {
         if (mView != null) {
             mWindowManager.removeView(mView);
             mView.setOnTouchListener(null);
@@ -135,13 +144,16 @@
         } else {
             Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
         }
+
+        mOrientationListener.disable();
     }
 
-    void onOrientationChanged() {
+    private void onOrientationChanged() {
         // If mView is null or if view is hidden, then return.
         if (mView == null || !mIsVisible) {
             return;
         }
+
         // If the overlay needs to be displayed with a new configuration, destroy the current
         // overlay, and re-create and show the overlay with the updated LayoutParams.
         hide();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 710aca0..f9103b5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -79,6 +79,8 @@
 
 import javax.inject.Inject;
 
+import kotlin.Unit;
+
 /**
  * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
  * and coordinates triggering of the high-brightness mode (HBM).
@@ -118,6 +120,7 @@
     @NonNull private final AccessibilityManager mAccessibilityManager;
     @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Nullable private final UdfpsHbmProvider mHbmProvider;
+    @VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
     @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -511,6 +514,10 @@
         mHbmProvider = hbmProvider.orElse(null);
         screenLifecycle.addObserver(mScreenObserver);
         mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON;
+        mOrientationListener = new BiometricOrientationEventListener(context, () -> {
+            onOrientationChanged();
+            return Unit.INSTANCE;
+        });
 
         mSensorProps = findFirstUdfps();
         // At least one UDFPS sensor exists
@@ -650,7 +657,7 @@
         return mCoreLayoutParams;
     }
 
-    void onOrientationChanged() {
+    private void onOrientationChanged() {
         // When the configuration changes it's almost always necessary to destroy and re-create
         // the overlay's window to pass it the new LayoutParams.
         // Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
@@ -668,6 +675,7 @@
         if (mView == null) {
             try {
                 Log.v(TAG, "showUdfpsOverlay | adding window reason=" + reason);
+
                 mView = (UdfpsView) mInflater.inflate(R.layout.udfps_view, null, false);
                 mOnFingerDown = false;
                 mView.setSensorProperties(mSensorProps);
@@ -675,6 +683,7 @@
                 UdfpsAnimationViewController animation = inflateUdfpsAnimation(reason);
                 animation.init();
                 mView.setAnimationViewController(animation);
+                mOrientationListener.enable();
 
                 // This view overlaps the sensor area, so prevent it from being selectable
                 // during a11y.
@@ -768,6 +777,8 @@
         } else {
             Log.v(TAG, "hideUdfpsOverlay | the overlay is already hidden");
         }
+
+        mOrientationListener.disable();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index ec96af3..e82f648 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -114,12 +114,10 @@
 
     @Override
     void onIlluminationStarting() {
-        setVisibility(View.INVISIBLE);
     }
 
     @Override
     void onIlluminationStopped() {
-        setVisibility(View.VISIBLE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 7ab4b6f..335ebca 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -501,6 +501,8 @@
                 views.setViewVisibility(R.id.availability, View.VISIBLE);
                 startPadding = mContext.getResources().getDimensionPixelSize(
                         R.dimen.availability_dot_shown_padding);
+                views.setContentDescription(R.id.availability,
+                        mContext.getString(R.string.person_available));
             } else {
                 views.setViewVisibility(R.id.availability, View.GONE);
                 startPadding = mContext.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 4b13015..04f089d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -150,11 +150,7 @@
         }
 
         List<CastDevice> activeDevices = getActiveDevices();
-        // We want to pop up the media route selection dialog if we either have no active devices
-        // (neither routes nor projection), or if we have an active route. In other cases, we assume
-        // that a projection is active. This is messy, but this tile never correctly handled the
-        // case where multiple devices were active :-/.
-        if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) {
+        if (willPopDetail()) {
             mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                 showDetail(true);
             });
@@ -163,6 +159,15 @@
         }
     }
 
+    // We want to pop up the media route selection dialog if we either have no active devices
+    // (neither routes nor projection), or if we have an active route. In other cases, we assume
+    // that a projection is active. This is messy, but this tile never correctly handled the
+    // case where multiple devices were active :-/.
+    private boolean willPopDetail() {
+        List<CastDevice> activeDevices = getActiveDevices();
+        return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo);
+    }
+
     private List<CastDevice> getActiveDevices() {
         ArrayList<CastDevice> activeDevices = new ArrayList<>();
         for (CastDevice device : mController.getCastDevices()) {
@@ -234,10 +239,12 @@
             state.contentDescription = state.contentDescription + ","
                     + mContext.getString(R.string.accessibility_quick_settings_open_details);
             state.expandedAccessibilityClassName = Button.class.getName();
+            state.forceExpandIcon = willPopDetail();
         } else {
             state.state = Tile.STATE_UNAVAILABLE;
             String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
             state.secondaryLabel = noWifi;
+            state.forceExpandIcon = false;
         }
         state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
         mDetailAdapter.updateItems(devices);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 82b6c0c..ab81ac1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -153,9 +153,25 @@
         });
     }
 
+    @Nullable
+    private CharSequence getServiceLabelSafe() {
+        try {
+            return mController.getWalletClient().getServiceLabel();
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Failed to get the service label safely, recreating wallet client", e);
+            mController.reCreateWalletClient();
+            try {
+                return mController.getWalletClient().getServiceLabel();
+            } catch (RuntimeException e2) {
+                Log.e(TAG, "The QAW service label is broken.", e2);
+                return null;
+            }
+        }
+    }
+
     @Override
     protected void handleUpdateState(State state, Object arg) {
-        CharSequence label = mController.getWalletClient().getServiceLabel();
+        CharSequence label = getServiceLabelSafe();
         state.label = label == null ? mLabel : label;
         state.contentDescription = state.label;
         Drawable tileIcon = mController.getWalletClient().getTileIcon();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
index 51cc32a..356f67e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageTileSet.java
@@ -24,7 +24,6 @@
 import android.graphics.RenderNode;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.util.Log;
 
 import androidx.annotation.UiThread;
 
@@ -140,7 +139,6 @@
      *               getHeight()).
      */
     Bitmap toBitmap(Rect bounds) {
-        Log.d(TAG, "exporting with bounds: " + bounds);
         if (mTiles.isEmpty()) {
             return null;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index af0141c..3b03909 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -63,7 +63,7 @@
  * and bottom before saving/sharing/editing.
  */
 public class LongScreenshotActivity extends Activity {
-    private static final String TAG = "LongScreenshotActivity";
+    private static final String TAG = LogConfig.logTag(LongScreenshotActivity.class);
 
     public static final String EXTRA_CAPTURE_RESPONSE = "capture-response";
     private static final String KEY_SAVED_IMAGE_PATH = "saved-image-path";
@@ -113,7 +113,6 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        Log.d(TAG, "onCreate(savedInstanceState = " + savedInstanceState + ")");
         super.onCreate(savedInstanceState);
         setContentView(R.layout.long_screenshot);
 
@@ -161,9 +160,13 @@
 
     @Override
     public void onStart() {
-        Log.d(TAG, "onStart");
         super.onStart();
 
+        if (mPreview.getDrawable() != null) {
+            // We already have an image, so no need to try to load again.
+            return;
+        }
+
         if (mCacheLoadFuture != null) {
             Log.d(TAG, "mCacheLoadFuture != null");
             final ListenableFuture<ImageLoader.Result> future = mCacheLoadFuture;
@@ -194,7 +197,7 @@
     }
 
     private void onLongScreenshotReceived(LongScreenshot longScreenshot) {
-        Log.d(TAG, "onLongScreenshotReceived(longScreenshot=" + longScreenshot + ")");
+        Log.i(TAG, "Completed: " + longScreenshot);
         mLongScreenshot = longScreenshot;
         Drawable drawable = mLongScreenshot.getDrawable();
         mPreview.setImageDrawable(drawable);
@@ -249,7 +252,6 @@
     }
 
     private void onCachedImageLoaded(ImageLoader.Result imageResult) {
-        Log.d(TAG, "onCachedImageLoaded(imageResult=" + imageResult + ")");
         BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
         mPreview.setImageDrawable(drawable);
         mPreview.setAlpha(1f);
@@ -274,7 +276,6 @@
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-        Log.d(TAG, "onSaveInstanceState");
         super.onSaveInstanceState(outState);
         if (mSavedImagePath != null) {
             outState.putString(KEY_SAVED_IMAGE_PATH, mSavedImagePath.getPath());
@@ -282,14 +283,7 @@
     }
 
     @Override
-    protected void onPause() {
-        Log.d(TAG, "onPause");
-        super.onPause();
-    }
-
-    @Override
     protected void onStop() {
-        Log.d(TAG, "onStop finishing=" + isFinishing());
         super.onStop();
         if (mTransitionStarted) {
             finish();
@@ -311,19 +305,12 @@
             mCacheSaveFuture.cancel(true);
         }
         if (mSavedImagePath != null) {
-            Log.d(TAG, "Deleting " + mSavedImagePath);
             //noinspection ResultOfMethodCallIgnored
             mSavedImagePath.delete();
             mSavedImagePath = null;
         }
     }
 
-    @Override
-    protected void onDestroy() {
-        Log.d(TAG, "onDestroy");
-        super.onDestroy();
-    }
-
     private void setButtonsEnabled(boolean enabled) {
         mSave.setEnabled(enabled);
         mEdit.setEnabled(enabled);
@@ -392,7 +379,6 @@
     }
 
     private void startExport(PendingAction action) {
-        Log.d(TAG, "startExport(action = " + action + ")");
         Drawable drawable = mPreview.getDrawable();
         if (drawable == null) {
             Log.e(TAG, "No drawable, skipping export!");
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 4c1f6a1..6dc6874 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -43,7 +43,7 @@
  * Interaction controller between the UI and ScrollCaptureClient.
  */
 public class ScrollCaptureController {
-    private static final String TAG = "ScrollCaptureController";
+    private static final String TAG = LogConfig.logTag(ScrollCaptureController.class);
     private static final float MAX_PAGES_DEFAULT = 3f;
 
     private static final String SETTING_KEY_MAX_PAGES = "screenshot.scroll_max_pages";
@@ -90,7 +90,9 @@
 
         /** Releases image resources from the screenshot. */
         public void release() {
-            Log.d(TAG, "LongScreenshot :: release()");
+            if (LogConfig.DEBUG_SCROLL) {
+                Log.d(TAG, "LongScreenshot :: release()");
+            }
             mImageTileSet.clear();
             mSession.release();
         }
@@ -153,15 +155,11 @@
      * @return a future ImageTile set containing the result
      */
     ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
-        Log.d(TAG, "run: " + response);
         return CallbackToFutureAdapter.getFuture(completer -> {
-            Log.d(TAG, "getFuture(ImageTileSet) ");
             mCaptureCompleter = completer;
             mBgExecutor.execute(() -> {
-                Log.d(TAG, "bgExecutor.execute");
                 float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
                         SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
-                Log.d(TAG, "client start, maxPages=" + maxPages);
                 mSessionFuture = mClient.start(response, maxPages);
                 mSessionFuture.addListener(this::onStartComplete, mContext.getMainExecutor());
             });
@@ -172,21 +170,27 @@
     private void onStartComplete() {
         try {
             mSession = mSessionFuture.get();
-            Log.d(TAG, "got session " + mSession);
+            if (LogConfig.DEBUG_SCROLL) {
+                Log.d(TAG, "got session " + mSession);
+            }
             requestNextTile(0);
         } catch (InterruptedException | ExecutionException e) {
             // Failure to start, propagate to caller
-            Log.d(TAG, "session start failed!");
+            Log.e(TAG, "session start failed!");
             mCaptureCompleter.setException(e);
         }
     }
 
     private void requestNextTile(int topPx) {
-        Log.d(TAG, "requestNextTile: " + topPx);
+        if (LogConfig.DEBUG_SCROLL) {
+            Log.d(TAG, "requestNextTile: " + topPx);
+        }
         mTileFuture = mSession.requestTile(topPx);
         mTileFuture.addListener(() -> {
             try {
-                Log.d(TAG, "onCaptureResult");
+                if (LogConfig.DEBUG_SCROLL) {
+                    Log.d(TAG, "onCaptureResult");
+                }
                 onCaptureResult(mTileFuture.get());
             } catch (InterruptedException | ExecutionException e) {
                 Log.e(TAG, "requestTile failed!", e);
@@ -196,14 +200,18 @@
     }
 
     private void onCaptureResult(CaptureResult result) {
-        Log.d(TAG, "onCaptureResult: " + result + " scrolling " + (mScrollingUp ? "UP" : "DOWN")
-                + " finish on boundary: " + mFinishOnBoundary);
+        if (LogConfig.DEBUG_SCROLL) {
+            Log.d(TAG, "onCaptureResult: " + result + " scrolling " + (mScrollingUp ? "UP" : "DOWN")
+                    + " finish on boundary: " + mFinishOnBoundary);
+        }
         boolean emptyResult = result.captured.height() == 0;
 
         if (emptyResult) {
             // Potentially reached a vertical boundary. Extend in the other direction.
             if (mFinishOnBoundary) {
-                Log.d(TAG, "Empty: finished!");
+                if (LogConfig.DEBUG_SCROLL) {
+                    Log.d(TAG, "Empty: finished!");
+                }
                 finishCapture();
                 return;
             } else {
@@ -212,13 +220,17 @@
                 mImageTileSet.clear();
                 mFinishOnBoundary = true;
                 mScrollingUp = !mScrollingUp;
-                Log.d(TAG, "Empty: cleared, switch direction to finish");
+                if (LogConfig.DEBUG_SCROLL) {
+                    Log.d(TAG, "Empty: cleared, switch direction to finish");
+                }
             }
         } else {
             // Got a non-empty result, but may already have enough bitmap data now
             int expectedTiles = mImageTileSet.size() + 1;
             if (expectedTiles >= mSession.getMaxTiles()) {
-                Log.d(TAG, "Hit max tiles: finished");
+                if (LogConfig.DEBUG_SCROLL) {
+                    Log.d(TAG, "Hit max tiles: finished");
+                }
                 // If we ever hit the max tiles, we've got enough bitmap data to finish
                 // (even if we weren't sure we'd finish on this pass).
                 finishCapture();
@@ -229,7 +241,9 @@
                     // by IDEAL_PORTION_ABOVE.
                     if (mImageTileSet.getHeight() + result.captured.height()
                             >= mSession.getTargetHeight() * IDEAL_PORTION_ABOVE) {
-                        Log.d(TAG, "Hit ideal portion above: clear and switch direction");
+                        if (LogConfig.DEBUG_SCROLL) {
+                            Log.d(TAG, "Hit ideal portion above: clear and switch direction");
+                        }
                         // We got enough above the start point, now see how far down it can go.
                         mImageTileSet.clear();
                         mScrollingUp = false;
@@ -241,20 +255,25 @@
         if (!emptyResult) {
             mImageTileSet.addTile(new ImageTile(result.image, result.captured));
         }
-
-        Log.d(TAG, "bounds: " + mImageTileSet.getLeft() + "," + mImageTileSet.getTop()
-                + " - " +  mImageTileSet.getRight() + "," + mImageTileSet.getBottom()
-                + " (" + mImageTileSet.getWidth() + "x" + mImageTileSet.getHeight() + ")");
+        if (LogConfig.DEBUG_SCROLL) {
+            Log.d(TAG, "bounds: " + mImageTileSet.getLeft() + "," + mImageTileSet.getTop()
+                    + " - " + mImageTileSet.getRight() + "," + mImageTileSet.getBottom()
+                    + " (" + mImageTileSet.getWidth() + "x" + mImageTileSet.getHeight() + ")");
+        }
 
         Rect gapBounds = mImageTileSet.getGaps();
         if (!gapBounds.isEmpty()) {
-            Log.d(TAG, "Found gaps in tileset: " + gapBounds + ", requesting " + gapBounds.top);
+            if (LogConfig.DEBUG_SCROLL) {
+                Log.d(TAG, "Found gaps in tileset: " + gapBounds + ", requesting " + gapBounds.top);
+            }
             requestNextTile(gapBounds.top);
             return;
         }
 
         if (mImageTileSet.getHeight() >= mSession.getTargetHeight()) {
-            Log.d(TAG, "Target height reached.");
+            if (LogConfig.DEBUG_SCROLL) {
+                Log.d(TAG, "Target height reached.");
+            }
             finishCapture();
             return;
         }
@@ -275,10 +294,14 @@
     }
 
     private void finishCapture() {
-        Log.d(TAG, "finishCapture()");
+        if (LogConfig.DEBUG_SCROLL) {
+            Log.d(TAG, "finishCapture()");
+        }
         mEndFuture = mSession.end();
         mEndFuture.addListener(() -> {
-            Log.d(TAG, "endCapture completed");
+            if (LogConfig.DEBUG_SCROLL) {
+                Log.d(TAG, "endCapture completed");
+            }
             // Provide result to caller and complete the top-level future
             // Caller is responsible for releasing this resource (ImageReader/HardwareBuffers)
             mCaptureCompleter.set(new LongScreenshot(mSession, mImageTileSet));
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index 2477534..ef18df5 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -38,6 +38,10 @@
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
+import com.android.internal.util.FrameworkStatsLog.write
 
 /**
  * Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is
@@ -185,16 +189,25 @@
                     keyguardDismissUtil.executeWhenUnlocked({
                         bgHandler.postDelayed({
                             disableSensorPrivacy()
+                            write(PRIVACY_TOGGLE_DIALOG_INTERACTION,
+                                    PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE,
+                                    sensorUsePackageName)
                         }, UNLOCK_DELAY_MILLIS)
 
                         false
                     }, false, true)
                 } else {
                     disableSensorPrivacy()
+                    write(PRIVACY_TOGGLE_DIALOG_INTERACTION,
+                            PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE,
+                            sensorUsePackageName)
                 }
             }
             BUTTON_NEGATIVE -> {
                 unsuppressImmediately = false
+                write(PRIVACY_TOGGLE_DIALOG_INTERACTION,
+                        PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL,
+                        sensorUsePackageName)
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f22c139..4bd2716 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -412,6 +412,7 @@
     private boolean mBackwardScrollable;
     private NotificationShelf mShelf;
     private int mMaxDisplayedNotifications = -1;
+    private float mKeyguardBottomPadding = -1;
     private int mStatusBarHeight;
     private int mMinInteractionHeight;
     private final Rect mClipRect = new Rect();
@@ -736,6 +737,16 @@
             mDebugPaint.setColor(Color.YELLOW);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
 
+            y = (int) mMaxLayoutHeight;
+            mDebugPaint.setColor(Color.MAGENTA);
+            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+            if (mKeyguardBottomPadding >= 0) {
+                y = getHeight() - (int) mKeyguardBottomPadding;
+                mDebugPaint.setColor(Color.GRAY);
+                canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+            }
+
             y = getHeight() - getEmptyBottomMargin();
             mDebugPaint.setColor(Color.GREEN);
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -4771,6 +4782,16 @@
         }
     }
 
+    /**
+     * This is used for debugging only; it will be used to draw the otherwise invisible line which
+     * NotificationPanelViewController treats as the bottom when calculating how many notifications
+     * appear on the keyguard.
+     * Setting a negative number will disable rendering this line.
+     */
+    public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+        mKeyguardBottomPadding = keyguardBottomPadding;
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setShouldShowShelfOnly(boolean shouldShowShelfOnly) {
         mShouldShowShelfOnly = shouldShowShelfOnly;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a92682a..a47afb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1204,6 +1204,16 @@
         mNotificationListContainer.setMaxDisplayedNotifications(maxNotifications);
     }
 
+    /**
+     * This is used for debugging only; it will be used to draw the otherwise invisible line which
+     * NotificationPanelViewController treats as the bottom when calculating how many notifications
+     * appear on the keyguard.
+     * Setting a negative number will disable rendering this line.
+     */
+    public void setKeyguardBottomPadding(float keyguardBottomPadding) {
+        mView.setKeyguardBottomPadding(keyguardBottomPadding);
+    }
+
     public RemoteInputController.Delegate createDelegate() {
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
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 920e2a5..591d46b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -332,6 +332,8 @@
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final TapAgainViewController mTapAgainViewController;
     private boolean mShouldUseSplitNotificationShade;
+    // The bottom padding reserved for elements of the keyguard measuring notifications
+    private float mKeyguardNotificationBottomPadding;
     // Current max allowed keyguard notifications determined by measuring the panel
     private int mMaxAllowedKeyguardNotifications;
 
@@ -1189,9 +1191,12 @@
         if (mKeyguardShowing && !mKeyguardBypassController.getBypassEnabled()) {
             mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
                     mMaxAllowedKeyguardNotifications);
+            mNotificationStackScrollLayoutController.setKeyguardBottomPadding(
+                    mKeyguardNotificationBottomPadding);
         } else {
             // no max when not on the keyguard
             mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(-1);
+            mNotificationStackScrollLayoutController.setKeyguardBottomPadding(-1f);
         }
     }
 
@@ -1393,6 +1398,7 @@
 
         float bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
         bottomPadding = Math.max(lockIconPadding, bottomPadding);
+        mKeyguardNotificationBottomPadding = bottomPadding;
 
         float availableSpace =
                 mNotificationStackScrollLayoutController.getHeight()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 1f1817c..5e70d0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -21,7 +21,7 @@
 import android.database.DataSetObserver;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
-import android.os.UserManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
@@ -76,7 +76,6 @@
     private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
     private final KeyguardUserDetailAdapter mUserDetailAdapter;
     private NotificationPanelViewController mNotificationPanelViewController;
-    private UserManager mUserManager;
     UserSwitcherController.UserRecord mCurrentUser;
 
     // State info for the user switch and keyguard
@@ -115,7 +114,6 @@
             UserAvatarView view,
             Context context,
             @Main Resources resources,
-            UserManager userManager,
             ScreenLifecycle screenLifecycle,
             UserSwitcherController userSwitcherController,
             KeyguardStateController keyguardStateController,
@@ -129,7 +127,6 @@
         if (DEBUG) Log.d(TAG, "New KeyguardQsUserSwitchController");
         mContext = context;
         mResources = resources;
-        mUserManager = userManager;
         mScreenLifecycle = screenLifecycle;
         mUserSwitcherController = userSwitcherController;
         mKeyguardStateController = keyguardStateController;
@@ -227,47 +224,39 @@
             return;
         }
 
-        if (mCurrentUser == null) {
-            mView.setVisibility(View.GONE);
-            return;
-        }
-
-        mView.setVisibility(View.VISIBLE);
-
-        String currentUserName = mCurrentUser.info.name;
         String contentDescription = null;
-
-        if (!TextUtils.isEmpty(currentUserName)) {
+        if (mCurrentUser != null && mCurrentUser.info != null && !TextUtils.isEmpty(
+                mCurrentUser.info.name)) {
+            // If we know the current user's name, have TalkBack to announce "Signed in as [user
+            // name]" when the icon is selected
+            contentDescription = mContext.getString(R.string.accessibility_quick_settings_user,
+                    mCurrentUser.info.name);
+        } else {
+            // As a fallback, have TalkBack announce "Switch user"
             contentDescription = mContext.getString(
-                    R.string.accessibility_quick_settings_user,
-                    currentUserName);
+                    R.string.accessibility_multi_user_switch_switcher);
         }
 
         if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
             mView.setContentDescription(contentDescription);
         }
 
-        mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), mCurrentUser.resolveId());
+        int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
+        mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
     }
 
     Drawable getCurrentUserIcon() {
         Drawable drawable;
-        if (mCurrentUser.picture == null) {
-            if (mCurrentUser.isCurrent && mCurrentUser.isGuest) {
+        if (mCurrentUser == null || mCurrentUser.picture == null) {
+            if (mCurrentUser != null && mCurrentUser.isGuest) {
                 drawable = mContext.getDrawable(R.drawable.ic_avatar_guest_user);
             } else {
-                drawable = mAdapter.getIconDrawable(mContext, mCurrentUser);
+                drawable = mContext.getDrawable(R.drawable.ic_avatar_user);
             }
-            int iconColorRes;
-            if (mCurrentUser.isSwitchToEnabled) {
-                iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
-            } else {
-                iconColorRes = R.color.kg_user_switcher_restricted_avatar_icon_color;
-            }
+            int iconColorRes = R.color.kg_user_switcher_avatar_icon_color;
             drawable.setTint(mResources.getColor(iconColorRes, mContext.getTheme()));
         } else {
-            int avatarSize =
-                    (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
+            int avatarSize = (int) mResources.getDimension(R.dimen.kg_framed_avatar_size);
             drawable = new CircleFramedDrawable(mCurrentUser.picture, avatarSize);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 9434c5d..acbbde5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -707,7 +707,7 @@
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
         cb.setNoSims(mHasNoSubs, mSimDetected);
-        if (mProviderModelBehavior) {
+        if (mProviderModelSetting) {
             cb.setConnectivityStatus(mNoDefaultNetwork, !mInetCondition, mNoNetworksAvailable);
         }
         mWifiSignalController.notifyListeners(cb);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index bfcd131..e94f836 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -20,7 +20,9 @@
 import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -55,12 +57,13 @@
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableContext;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.WindowManager;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.internal.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.CommandQueue;
@@ -539,6 +542,17 @@
         verify(mUdfpsController).onAodInterrupt(eq(pos), eq(pos), eq(majorMinor), eq(majorMinor));
     }
 
+    @Test
+    public void testSubscribesToOrientationChangesWhenShowingDialog() {
+        assertFalse(mAuthController.mOrientationListener.getEnabled());
+
+        showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
+        assertTrue(mAuthController.mOrientationListener.getEnabled());
+
+        mAuthController.hideAuthenticationDialog();
+        assertFalse(mAuthController.mOrientationListener.getEnabled());
+    }
+
     // Helpers
 
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
new file mode 100644
index 0000000..7019a4b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.biometrics
+
+import android.hardware.biometrics.SensorProperties
+import android.hardware.display.DisplayManagerGlobal
+import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.hardware.fingerprint.ISidefpsController
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+import android.view.DisplayInfo
+import android.view.LayoutInflater
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+private const val DISPLAY_ID = 2
+private const val SENSOR_ID = 1
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class SidefpsControllerTest : SysuiTestCase() {
+
+    @JvmField @Rule
+    var rule = MockitoJUnit.rule()
+
+    @Mock
+    lateinit var layoutInflater: LayoutInflater
+    @Mock
+    lateinit var fingerprintManager: FingerprintManager
+    @Mock
+    lateinit var windowManager: WindowManager
+    @Mock
+    lateinit var sidefpsView: SidefpsView
+
+    private val executor = FakeExecutor(FakeSystemClock())
+    private lateinit var overlayController: ISidefpsController
+    private lateinit var sideFpsController: SidefpsController
+
+    @Before
+    fun setup() {
+        `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
+        `when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
+                listOf(
+                        FingerprintSensorPropertiesInternal(
+                                SENSOR_ID,
+                                SensorProperties.STRENGTH_STRONG,
+                                5 /* maxEnrollmentsPerUser */,
+                                listOf() /* componentInfo */,
+                                FingerprintSensorProperties.TYPE_POWER_BUTTON,
+                                true /* resetLockoutRequiresHardwareAuthToken */
+                        )
+                )
+        )
+        `when`(windowManager.defaultDisplay).thenReturn(
+                Display(
+                        DisplayManagerGlobal.getInstance(),
+                        DISPLAY_ID,
+                        DisplayInfo(),
+                        DEFAULT_DISPLAY_ADJUSTMENTS
+                )
+        )
+
+        sideFpsController = SidefpsController(
+                mContext, layoutInflater, fingerprintManager, windowManager, executor
+        )
+
+        overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java).apply {
+            verify(fingerprintManager).setSidefpsController(capture())
+        }.value
+    }
+
+    @Test
+    fun testSubscribesToOrientationChangesWhenShowingOverlay() {
+        assertThat(sideFpsController.mOrientationListener.enabled).isFalse()
+
+        overlayController.show()
+        executor.runAllReady()
+        assertThat(sideFpsController.mOrientationListener.enabled).isTrue()
+
+        overlayController.hide()
+        executor.runAllReady()
+        assertThat(sideFpsController.mOrientationListener.enabled).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 25722e1..d8d3676 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -236,6 +238,22 @@
     }
 
     @Test
+    public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
+        assertFalse(mUdfpsController.mOrientationListener.getEnabled());
+
+        mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+                IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        assertTrue(mUdfpsController.mOrientationListener.getEnabled());
+
+        mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+        mFgExecutor.runAllReady();
+
+        assertFalse(mUdfpsController.mOrientationListener.getEnabled());
+    }
+
+    @Test
     public void fingerDown() throws RemoteException {
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isIlluminationRequested()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index c818da8..b4b4597 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -289,6 +289,8 @@
         assertEquals(View.GONE, result.findViewById(R.id.last_interaction).getVisibility());
         // Has availability.
         assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
+        assertEquals(result.findViewById(R.id.availability).getContentDescription(),
+                mContext.getString(R.string.person_available));
         // Has person icon.
         assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
         // No status.
@@ -334,6 +336,8 @@
         assertEquals(View.GONE, largeResult.findViewById(R.id.last_interaction).getVisibility());
         // Has availability.
         assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+        assertEquals(largeResult.findViewById(R.id.availability).getContentDescription(),
+                mContext.getString(R.string.person_available));
         // Shows person icon.
         assertEquals(View.VISIBLE, largeResult.findViewById(R.id.person_icon).getVisibility());
         // No status.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index d44a526..e939411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -17,6 +17,7 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertEquals;
 
+import static org.junit.Assert.assertFalse;
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -327,4 +328,77 @@
         assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
         assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name));
     }
+
+    @Test
+    public void testExpandView_wifiNotConnected() {
+        mCastTile.refreshState();
+        mTestableLooper.processAllMessages();
+
+        assertFalse(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_wifiEnabledNotCasting() {
+        enableWifiAndProcessMessages();
+
+        assertTrue(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_casting_projection() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastController.CastDevice.STATE_CONNECTED;
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertFalse(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_connecting_projection() {
+        CastController.CastDevice connecting = new CastController.CastDevice();
+        connecting.state = CastDevice.STATE_CONNECTING;
+        connecting.name = "Test Casting Device";
+
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(connecting);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertFalse(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_casting_mediaRoute() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        device.tag = mock(MediaRouter.RouteInfo.class);
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertTrue(mCastTile.getState().forceExpandIcon);
+    }
+
+    @Test
+    public void testExpandView_connecting_mediaRoute() {
+        CastController.CastDevice connecting = new CastController.CastDevice();
+        connecting.state = CastDevice.STATE_CONNECTING;
+        connecting.tag = mock(RouteInfo.class);
+        connecting.name = "Test Casting Device";
+
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(connecting);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        assertTrue(mCastTile.getState().forceExpandIcon);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index a70c2be..17797b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -256,6 +257,19 @@
     }
 
     @Test
+    public void testGetServiceLabelUnsafe_recreateWalletClient() {
+        doAnswer(invocation -> {
+            throw new Exception("Bad service label.");
+        }).when(mQuickAccessWalletClient).getServiceLabel();
+
+        QSTile.State state = new QSTile.State();
+
+        mTile.handleUpdateState(state, null);
+
+        verify(mController).reCreateWalletClient();
+    }
+
+    @Test
     public void testHandleUpdateState_updateLabelAndIcon() {
         QSTile.State state = new QSTile.State();
 
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 50b27a0..d04698c 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -519,15 +519,6 @@
         // user has completed setup.
         return intercept && isUserSetupComplete();
     }
-
-    public boolean isCameraDoubleTapPowerEnabled() {
-        return mCameraDoubleTapPowerEnabled;
-    }
-
-    public boolean isEmergencyGestureEnabled() {
-        return mEmergencyGestureEnabled;
-    }
-
     /**
      * @return true if camera was launched, false otherwise.
      */
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7789d60..33bc212 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1090,8 +1090,7 @@
          */
         private void finishOrPause(@NonNull IBinder clientId, boolean triggerCallbackIfNeeded,
                 boolean isPausing) {
-            int indexOfToken = mInProgressEvents != null
-                    ? mInProgressEvents.indexOfKey(clientId) : -1;
+            int indexOfToken = isRunning() ? mInProgressEvents.indexOfKey(clientId) : -1;
             if (indexOfToken < 0) {
                 finishPossiblyPaused(clientId, isPausing);
                 return;
@@ -1145,7 +1144,7 @@
 
         // Finish or pause (no-op) an already paused op
         private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
-            if (mPausedInProgressEvents == null) {
+            if (!isPaused()) {
                 Slog.wtf(TAG, "No ops running or paused");
                 return;
             }
@@ -1186,7 +1185,7 @@
          * Pause all currently started ops. This will create a HistoricalRegistry
          */
         public void pause() {
-            if (mInProgressEvents == null) {
+            if (!isRunning()) {
                 return;
             }
 
@@ -1211,7 +1210,7 @@
          * times, but keep all other values the same
          */
         public void resume() {
-            if (mPausedInProgressEvents == null) {
+            if (!isPaused()) {
                 return;
             }
 
@@ -1245,7 +1244,7 @@
          */
         void onClientDeath(@NonNull IBinder clientId) {
             synchronized (AppOpsService.this) {
-                if (mInProgressEvents == null && mPausedInProgressEvents == null) {
+                if (!isPaused() && !isRunning()) {
                     return;
                 }
 
@@ -1266,7 +1265,7 @@
          * @param newState The new state
          */
         public void onUidStateChanged(@AppOpsManager.UidState int newState) {
-            if (mInProgressEvents == null && mPausedInProgressEvents == null) {
+            if (!isPaused() && !isRunning()) {
                 return;
             }
 
@@ -1350,12 +1349,15 @@
          * @param opToAdd The op to add
          */
         public void add(@NonNull AttributedOp opToAdd) {
-            if (opToAdd.mInProgressEvents != null) {
-                Slog.w(TAG, "Ignoring " + opToAdd.mInProgressEvents.size() + " running app-ops");
+            if (opToAdd.isRunning() || opToAdd.isPaused()) {
+                ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents = opToAdd.isRunning()
+                        ? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
+                Slog.w(TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
+                        + opToAdd.isRunning());
 
-                int numInProgressEvents = opToAdd.mInProgressEvents.size();
+                int numInProgressEvents = ignoredEvents.size();
                 for (int i = 0; i < numInProgressEvents; i++) {
-                    InProgressStartOpEvent event = opToAdd.mInProgressEvents.valueAt(i);
+                    InProgressStartOpEvent event = ignoredEvents.valueAt(i);
 
                     event.finish();
                     mInProgressStartOpEventPool.release(event);
@@ -1367,11 +1369,11 @@
         }
 
         public boolean isRunning() {
-            return mInProgressEvents != null;
+            return mInProgressEvents != null && !mInProgressEvents.isEmpty();
         }
 
         public boolean isPaused() {
-            return mPausedInProgressEvents != null;
+            return mPausedInProgressEvents != null && !mPausedInProgressEvents.isEmpty();
         }
 
         boolean hasAnyTime() {
@@ -1401,7 +1403,7 @@
             LongSparseArray<NoteOpEvent> accessEvents = deepClone(mAccessEvents);
 
             // Add in progress events as access events
-            if (mInProgressEvents != null) {
+            if (isRunning()) {
                 long now = SystemClock.elapsedRealtime();
                 int numInProgressEvents = mInProgressEvents.size();
 
@@ -2041,9 +2043,12 @@
                             attributionNum++) {
                         AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
 
-                        while (attributedOp.mInProgressEvents != null) {
+                        while (attributedOp.isRunning()) {
                             attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
                         }
+                        while (attributedOp.isPaused()) {
+                            attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1acbde9..565c9ae 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -24,8 +24,7 @@
 import static android.os.PowerWhitelistManager.REASON_VPN;
 import static android.os.UserHandle.PER_USER_RANGE;
 
-import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
+import static java.util.Objects.requireNonNull;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -1098,13 +1097,14 @@
             return Process.myUid();
         }
         PackageManager pm = mContext.getPackageManager();
-        return Binder.withCleanCallingIdentity(() -> {
-            try {
-                return pm.getPackageUidAsUser(app, userId);
-            } catch (NameNotFoundException e) {
-                return -1;
-            }
-        });
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return pm.getPackageUidAsUser(app, userId);
+        } catch (NameNotFoundException e) {
+            return -1;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     private boolean doesPackageTargetAtLeastQ(String packageName) {
@@ -1280,15 +1280,16 @@
                 // We are user controlled, not driven by NetworkRequest.
             }
         };
-        Binder.withCleanCallingIdentity(() -> {
-            try {
-                mNetworkAgent.register();
-            } catch (final Exception e) {
-                // If register() throws, don't keep an unregistered agent.
-                mNetworkAgent = null;
-                throw e;
-            }
-        });
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mNetworkAgent.register();
+        } catch (final Exception e) {
+            // If register() throws, don't keep an unregistered agent.
+            mNetworkAgent = null;
+            throw e;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
         mNetworkAgent.setUnderlyingNetworks((mConfig.underlyingNetworks != null)
                 ? Arrays.asList(mConfig.underlyingNetworks) : null);
         updateState(DetailedState.CONNECTED, "agentConnect");
@@ -2026,13 +2027,16 @@
     }
 
     private void enforceNotRestrictedUser() {
-        Binder.withCleanCallingIdentity(() -> {
+        final long token = Binder.clearCallingIdentity();
+        try {
             final UserInfo user = mUserManager.getUserInfo(mUserId);
 
             if (user.isRestricted()) {
                 throw new SecurityException("Restricted users cannot configure VPNs");
             }
-        });
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
@@ -2825,8 +2829,10 @@
 
         LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
             super(TAG);
-            checkArgument(racoon != null || mtpd != null, "Arguments to racoon and mtpd "
-                    + "must not both be null");
+            if (racoon == null && mtpd == null) {
+                throw new IllegalArgumentException(
+                        "Arguments to racoon and mtpd must not both be null");
+            }
             mConfig = config;
             mDaemons = new String[] {"racoon", "mtpd"};
             // TODO: clear arguments from memory once launched
@@ -3151,8 +3157,8 @@
      */
     public synchronized boolean provisionVpnProfile(
             @NonNull String packageName, @NonNull VpnProfile profile) {
-        checkNotNull(packageName, "No package name provided");
-        checkNotNull(profile, "No profile provided");
+        requireNonNull(packageName, "No package name provided");
+        requireNonNull(profile, "No profile provided");
 
         verifyCallingUidAndPackage(packageName);
         enforceNotRestrictedUser();
@@ -3169,12 +3175,12 @@
         }
 
         // Permissions checked during startVpnProfile()
-        Binder.withCleanCallingIdentity(
-                () -> {
-                    getVpnProfileStore().put(
-                            getProfileNameForPackage(packageName),
-                            encodedProfile);
-                });
+        final long token = Binder.clearCallingIdentity();
+        try {
+            getVpnProfileStore().put(getProfileNameForPackage(packageName), encodedProfile);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
 
         // TODO: if package has CONTROL_VPN, grant the ACTIVATE_PLATFORM_VPN appop.
         // This mirrors the prepareAndAuthorize that is used by VpnService.
@@ -3194,26 +3200,28 @@
      */
     public synchronized void deleteVpnProfile(
             @NonNull String packageName) {
-        checkNotNull(packageName, "No package name provided");
+        requireNonNull(packageName, "No package name provided");
 
         verifyCallingUidAndPackage(packageName);
         enforceNotRestrictedUser();
 
-        Binder.withCleanCallingIdentity(
-                () -> {
-                    // If this profile is providing the current VPN, turn it off, disabling
-                    // always-on as well if enabled.
-                    if (isCurrentIkev2VpnLocked(packageName)) {
-                        if (mAlwaysOn) {
-                            // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
-                            setAlwaysOnPackage(null, false, null);
-                        } else {
-                            prepareInternal(VpnConfig.LEGACY_VPN);
-                        }
-                    }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // If this profile is providing the current VPN, turn it off, disabling
+            // always-on as well if enabled.
+            if (isCurrentIkev2VpnLocked(packageName)) {
+                if (mAlwaysOn) {
+                    // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
+                    setAlwaysOnPackage(null, false, null);
+                } else {
+                    prepareInternal(VpnConfig.LEGACY_VPN);
+                }
+            }
 
-                    getVpnProfileStore().remove(getProfileNameForPackage(packageName));
-                });
+            getVpnProfileStore().remove(getProfileNameForPackage(packageName));
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     /**
@@ -3247,7 +3255,7 @@
      */
     public synchronized void startVpnProfile(
             @NonNull String packageName) {
-        checkNotNull(packageName, "No package name provided");
+        requireNonNull(packageName, "No package name provided");
 
         enforceNotRestrictedUser();
 
@@ -3256,15 +3264,17 @@
             throw new SecurityException("User consent not granted for package " + packageName);
         }
 
-        Binder.withCleanCallingIdentity(
-                () -> {
-                    final VpnProfile profile = getVpnProfilePrivileged(packageName);
-                    if (profile == null) {
-                        throw new IllegalArgumentException("No profile found for " + packageName);
-                    }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final VpnProfile profile = getVpnProfilePrivileged(packageName);
+            if (profile == null) {
+                throw new IllegalArgumentException("No profile found for " + packageName);
+            }
 
-                    startVpnProfilePrivileged(profile, packageName);
-                });
+            startVpnProfilePrivileged(profile, packageName);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
     }
 
     private synchronized void startVpnProfilePrivileged(
@@ -3325,7 +3335,7 @@
      * @param packageName the package name of the app provisioning this profile
      */
     public synchronized void stopVpnProfile(@NonNull String packageName) {
-        checkNotNull(packageName, "No package name provided");
+        requireNonNull(packageName, "No package name provided");
 
         enforceNotRestrictedUser();
 
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index a4a5f96..cb2cd14 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -778,7 +778,6 @@
                     mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
             mScreenDarkeningThreshold = clampScreenBrightness(
                     mScreenBrightnessThresholds.getDarkeningThreshold(newScreenAutoBrightness));
-            mHbmController.onAutoBrightnessChanged(mScreenAutoBrightness);
 
             if (sendUpdate) {
                 mCallbacks.updateBrightness();
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9d8ca9a..7d06d6e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -705,10 +705,8 @@
             final BrightnessPair brightnessPair =
                     index < 0 ? null : mDisplayBrightnesses.valueAt(index);
             if (index < 0 || (mDisplayStates.valueAt(index) == state
-                    && BrightnessSynchronizer.floatEquals(
-                            brightnessPair.brightness, brightnessState)
-                    && BrightnessSynchronizer.floatEquals(
-                            brightnessPair.sdrBrightness, sdrBrightnessState))) {
+                    && brightnessPair.brightness == brightnessState
+                    && brightnessPair.sdrBrightness == sdrBrightnessState)) {
                 return; // Display no longer exists or no change.
             }
 
@@ -1281,6 +1279,11 @@
         sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         scheduleTraversalLocked(false);
         mPersistentDataStore.saveIfNeeded();
+
+        DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
+        if (dpc != null) {
+            dpc.onDisplayChanged();
+        }
     }
 
     private void handleLogicalDisplayFrameRateOverridesChangedLocked(
@@ -1312,11 +1315,6 @@
         if (work != null) {
             mHandler.post(work);
         }
-        final int displayId = display.getDisplayIdLocked();
-        DisplayPowerController dpc = mDisplayPowerControllers.get(displayId);
-        if (dpc != null) {
-            dpc.onDisplayChanged();
-        }
         handleLogicalDisplayChangedLocked(display);
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 20d364e..b6d65197 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -511,7 +511,7 @@
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 com.android.internal.R.bool.config_skipScreenOnBrightnessRamp);
 
-        mHbmController = createHbmController();
+        mHbmController = createHbmControllerLocked();
 
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
@@ -717,6 +717,7 @@
         final String uniqueId = device.getUniqueId();
         final DisplayDeviceConfig config = device.getDisplayDeviceConfig();
         final IBinder token = device.getDisplayTokenLocked();
+        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
         mHandler.post(() -> {
             if (mDisplayDevice == device) {
                 return;
@@ -724,7 +725,7 @@
             mDisplayDevice = device;
             mUniqueDisplayId = uniqueId;
             mDisplayDeviceConfig = config;
-            loadFromDisplayDeviceConfig(token);
+            loadFromDisplayDeviceConfig(token, info);
         });
     }
 
@@ -765,7 +766,7 @@
         }
     }
 
-    private void loadFromDisplayDeviceConfig(IBinder token) {
+    private void loadFromDisplayDeviceConfig(IBinder token, DisplayDeviceInfo info) {
         // All properties that depend on the associated DisplayDevice and the DDC must be
         // updated here.
         loadAmbientLightSensor();
@@ -774,7 +775,8 @@
         loadNitsRange(mContext.getResources());
         setUpAutoBrightness(mContext.getResources(), mHandler);
         reloadReduceBrightColours();
-        mHbmController.resetHbmData(token, mDisplayDeviceConfig.getHighBrightnessModeData());
+        mHbmController.resetHbmData(info.width, info.height, token,
+                mDisplayDeviceConfig.getHighBrightnessModeData(), mBrightnessSetting);
     }
 
     private void sendUpdatePowerState() {
@@ -1343,15 +1345,15 @@
             if (mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
                     && ((mBrightnessReason.modifier & BrightnessReason.MODIFIER_DIMMED) == 0
                     || (mBrightnessReason.modifier & BrightnessReason.MODIFIER_LOW_POWER) == 0)) {
+                // We want to scale HDR brightness level with the SDR level
                 animateValue = mHbmController.getHdrBrightnessValue();
             }
 
             final float currentBrightness = mPowerState.getScreenBrightness();
             final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
             if (isValidBrightnessValue(animateValue)
-                    && (!BrightnessSynchronizer.floatEquals(animateValue, currentBrightness)
-                    || !BrightnessSynchronizer.floatEquals(
-                            sdrAnimateValue, currentSdrBrightness))) {
+                    && (animateValue != currentBrightness
+                    || sdrAnimateValue != currentSdrBrightness)) {
                 if (initialRampSkip || hasBrightnessBuckets
                         || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
                     animateScreenBrightness(animateValue, sdrAnimateValue,
@@ -1514,21 +1516,22 @@
         }
     }
 
-    private HighBrightnessModeController createHbmController() {
-        final DisplayDeviceConfig ddConfig =
-                mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayDeviceConfig();
+    private HighBrightnessModeController createHbmControllerLocked() {
+        final DisplayDevice device = mLogicalDisplay.getPrimaryDisplayDeviceLocked();
+        final DisplayDeviceConfig ddConfig = device.getDisplayDeviceConfig();
         final IBinder displayToken =
                 mLogicalDisplay.getPrimaryDisplayDeviceLocked().getDisplayTokenLocked();
         final DisplayDeviceConfig.HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
-        return new HighBrightnessModeController(mHandler, displayToken, PowerManager.BRIGHTNESS_MIN,
-                PowerManager.BRIGHTNESS_MAX, hbmData,
+        final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken,
+                PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData,
                 () -> {
                     sendUpdatePowerStateLocked();
                     mHandler.post(mOnBrightnessChangeRunnable);
                     // TODO(b/192258832): Switch the HBMChangeCallback to a listener pattern.
                     mAutomaticBrightnessController.update();
-                }, mContext);
+                }, mContext, mBrightnessSetting);
     }
 
     private void blockScreenOn() {
@@ -1680,11 +1683,10 @@
                 mHbmController.getCurrentBrightnessMin(), mHbmController.getCurrentBrightnessMax());
     }
 
-    // Checks whether the brightness is within the valid brightness range, not including the off or
-    // invalid states.
-    private boolean isValidBrightnessValue(float brightnessState) {
-        return brightnessState >= PowerManager.BRIGHTNESS_MIN
-                && brightnessState <= PowerManager.BRIGHTNESS_MAX;
+    // Checks whether the brightness is within the valid brightness range, not including off.
+    private boolean isValidBrightnessValue(float brightness) {
+        return brightness >= PowerManager.BRIGHTNESS_MIN
+                && brightness <= PowerManager.BRIGHTNESS_MAX;
     }
 
     private void animateScreenBrightness(float target, float sdrTarget, float rate) {
@@ -2014,6 +2016,9 @@
     }
 
     private void putScreenBrightnessSetting(float brightnessValue, boolean updateCurrent) {
+        if (!isValidBrightnessValue(brightnessValue)) {
+            return;
+        }
         if (updateCurrent) {
             setCurrentScreenBrightness(brightnessValue);
         }
@@ -2060,8 +2065,7 @@
                 || mPendingScreenBrightnessSetting < 0.0f)) {
             return brightnessSplineChanged;
         }
-        if (BrightnessSynchronizer.floatEquals(
-                mCurrentScreenBrightnessSetting, mPendingScreenBrightnessSetting)) {
+        if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
             mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             return brightnessSplineChanged;
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index b58dd38..6af1923 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -26,8 +26,6 @@
 import android.view.Choreographer;
 import android.view.Display;
 
-import com.android.internal.display.BrightnessSynchronizer;
-
 import java.io.PrintWriter;
 
 /**
@@ -166,10 +164,11 @@
     /**
      * Sets the display's SDR brightness.
      *
-     * @param brightness The brightness, ranges from 0.0f (minimum / off) to 1.0f (brightest).
+     * @param brightness The brightness, ranges from 0.0f (minimum) to 1.0f (brightest), or is -1f
+     *                   (off).
      */
     public void setSdrScreenBrightness(float brightness) {
-        if (!BrightnessSynchronizer.floatEquals(mSdrScreenBrightness, brightness)) {
+        if (mSdrScreenBrightness != brightness) {
             if (DEBUG) {
                 Slog.d(TAG, "setSdrScreenBrightness: brightness=" + brightness);
             }
@@ -192,10 +191,11 @@
     /**
      * Sets the display brightness.
      *
-     * @param brightness The brightness, ranges from 0.0f (minimum / off) to 1.0f (brightest).
+     * @param brightness The brightness, ranges from 0.0f (minimum) to 1.0f (brightest), or is -1f
+     *                   (off).
      */
     public void setScreenBrightness(float brightness) {
-        if (!BrightnessSynchronizer.floatEquals(mScreenBrightness, brightness)) {
+        if (mScreenBrightness != brightness) {
             if (DEBUG) {
                 Slog.d(TAG, "setScreenBrightness: brightness=" + brightness);
             }
@@ -432,10 +432,8 @@
         public boolean setState(int state, float brightnessState, float sdrBrightnessState) {
             synchronized (mLock) {
                 boolean stateChanged = state != mPendingState;
-                boolean backlightChanged =
-                        !BrightnessSynchronizer.floatEquals(brightnessState, mPendingBacklight)
-                        || !BrightnessSynchronizer.floatEquals(
-                                sdrBrightnessState, mPendingSdrBacklight);
+                boolean backlightChanged = brightnessState != mPendingBacklight
+                        || sdrBrightnessState != mPendingSdrBacklight;
                 if (stateChanged || backlightChanged) {
                     if (DEBUG) {
                         Slog.d(TAG, "Requesting new screen state: state="
@@ -486,10 +484,8 @@
                     stateChanged = (state != mActualState);
                     brightnessState = mPendingBacklight;
                     sdrBrightnessState = mPendingSdrBacklight;
-                    backlightChanged =
-                            !BrightnessSynchronizer.floatEquals(brightnessState, mActualBacklight)
-                            || !BrightnessSynchronizer.floatEquals(
-                                    sdrBrightnessState, mActualSdrBacklight);
+                    backlightChanged = brightnessState != mActualBacklight
+                            || sdrBrightnessState != mActualSdrBacklight;
                     if (!stateChanged) {
                         // State changed applied, notify outer class.
                         postScreenUpdateThreadSafe();
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index d629422..e0b4d47 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -31,11 +31,13 @@
 import android.os.Temperature;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.MathUtils;
 import android.util.Slog;
 import android.util.TimeUtils;
 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;
 
@@ -56,6 +58,8 @@
 
     private static final boolean DEBUG = false;
 
+    private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f;
+
     private final float mBrightnessMin;
     private final float mBrightnessMax;
     private final Handler mHandler;
@@ -66,6 +70,7 @@
     private final Context mContext;
     private final SettingsObserver mSettingsObserver;
     private final Injector mInjector;
+    private final BrightnessSettingListener mBrightnessSettingListener = this::onBrightnessChanged;
 
     private SurfaceControlHdrLayerInfoListener mHdrListener;
     private HighBrightnessModeData mHbmData;
@@ -74,11 +79,15 @@
     private boolean mIsInAllowedAmbientRange = false;
     private boolean mIsTimeAvailable = false;
     private boolean mIsAutoBrightnessEnabled = false;
-    private float mAutoBrightness;
+    private float mBrightness;
     private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
     private boolean mIsHdrLayerPresent = false;
     private boolean mIsThermalStatusWithinLimit = true;
     private boolean mIsBlockedByLowPowerMode = false;
+    private int mWidth;
+    private int mHeight;
+    private BrightnessSetting mBrightnessSetting;
+    private float mAmbientLux;
 
     /**
      * If HBM is currently running, this is the start time for the current HBM session.
@@ -92,30 +101,32 @@
      */
     private LinkedList<HbmEvent> mEvents = new LinkedList<>();
 
-    HighBrightnessModeController(Handler handler, IBinder displayToken, float brightnessMin,
-            float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
-            Context context) {
-        this(new Injector(), handler, displayToken, brightnessMin, brightnessMax,
-                hbmData, hbmChangeCallback, context);
+    HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
+            float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
+            Runnable hbmChangeCallback, Context context, BrightnessSetting brightnessSetting) {
+        this(new Injector(), handler, width, height, displayToken, brightnessMin, brightnessMax,
+                hbmData, hbmChangeCallback, context, brightnessSetting);
     }
 
     @VisibleForTesting
-    HighBrightnessModeController(Injector injector, Handler handler, IBinder displayToken,
-            float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData,
-            Runnable hbmChangeCallback, Context context) {
+    HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
+            IBinder displayToken, float brightnessMin, float brightnessMax,
+            HighBrightnessModeData hbmData, Runnable hbmChangeCallback,
+            Context context, BrightnessSetting brightnessSetting) {
         mInjector = injector;
+        mContext = context;
         mClock = injector.getClock();
         mHandler = handler;
         mBrightnessMin = brightnessMin;
         mBrightnessMax = brightnessMax;
+        mBrightness = brightnessSetting.getBrightness();
         mHbmChangeCallback = hbmChangeCallback;
-        mContext = context;
-        mAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
-        mRecalcRunnable = this::recalculateTimeAllowance;
-        mHdrListener = new HdrListener();
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
         mSettingsObserver = new SettingsObserver(mHandler);
-        resetHbmData(displayToken, hbmData);
+        mRecalcRunnable = this::recalculateTimeAllowance;
+        mHdrListener = new HdrListener();
+
+        resetHbmData(width, height, displayToken, hbmData, brightnessSetting);
     }
 
     void setAutoBrightnessEnabled(boolean isEnabled) {
@@ -123,7 +134,7 @@
             return;
         }
         if (DEBUG) {
-            Slog.d(TAG, "setAutoBrightness( " + isEnabled + " )");
+            Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )");
         }
         mIsAutoBrightnessEnabled = isEnabled;
         mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
@@ -147,11 +158,22 @@
         }
     }
 
+    float getNormalBrightnessMax() {
+        return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax;
+    }
+
     float getHdrBrightnessValue() {
-        return mBrightnessMax;
+        // For HDR brightness, we take the current brightness and scale it to the max. The reason
+        // we do this is because we want brightness to go to HBM max when it would normally go
+        // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition
+        // point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should
+        // automatically scale the brightness without forcing the user to adjust to higher values.
+        return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(),
+                mBrightnessMin, mBrightnessMax, mBrightness);
     }
 
     void onAmbientLuxChange(float ambientLux) {
+        mAmbientLux = ambientLux;
         if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
             return;
         }
@@ -163,17 +185,17 @@
         }
     }
 
-    void onAutoBrightnessChanged(float autoBrightness) {
+    @VisibleForTesting
+    void onBrightnessChanged(float brightness) {
         if (!deviceSupportsHbm()) {
             return;
         }
-        final float oldAutoBrightness = mAutoBrightness;
-        mAutoBrightness = autoBrightness;
+        mBrightness = brightness;
 
         // If we are starting or ending a high brightness mode session, store the current
         // session in mRunningStartTimeMillis, or the old one in mEvents.
         final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1;
-        final boolean shouldHbmDrainAvailableTime = mAutoBrightness > mHbmData.transitionPoint
+        final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
                 && !mIsHdrLayerPresent;
         if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
             final long currentTime = mClock.uptimeMillis();
@@ -202,8 +224,12 @@
         mSettingsObserver.stopObserving();
     }
 
-    void resetHbmData(IBinder displayToken, HighBrightnessModeData hbmData) {
+    void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData,
+            BrightnessSetting brightnessSetting) {
+        mWidth = width;
+        mHeight = height;
         mHbmData = hbmData;
+        resetBrightnessSetting(brightnessSetting);
         unregisterHdrListener();
         mSkinThermalStatusObserver.stopObserving();
         mSettingsObserver.stopObserving();
@@ -227,21 +253,23 @@
 
     private void dumpLocal(PrintWriter pw) {
         pw.println("HighBrightnessModeController:");
+        pw.println("  mBrightness=" + mBrightness);
         pw.println("  mCurrentMin=" + getCurrentBrightnessMin());
         pw.println("  mCurrentMax=" + getCurrentBrightnessMax());
         pw.println("  mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode));
-        pw.println("  remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
         pw.println("  mHbmData=" + mHbmData);
+        pw.println("  mAmbientLux=" + mAmbientLux);
         pw.println("  mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
-        pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
         pw.println("  mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
-        pw.println("  mAutoBrightness=" + mAutoBrightness);
         pw.println("  mIsHdrLayerPresent=" + mIsHdrLayerPresent);
         pw.println("  mBrightnessMin=" + mBrightnessMin);
         pw.println("  mBrightnessMax=" + mBrightnessMax);
+        pw.println("  remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
+        pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
         pw.println("  mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis));
         pw.println("  mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
         pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
+        pw.println("  width*height=" + mWidth + "*" + mHeight);
         pw.println("  mEvents=");
         final long currentTime = mClock.uptimeMillis();
         long lastStartTime = currentTime;
@@ -268,9 +296,26 @@
         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() {
-        return mIsHdrLayerPresent
-                || (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
+        // 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
+        // HDR layer because we don't want the brightness MAX to change for HDR, which has its
+        // brightness scaled in a different way than sunlight HBM that doesn't require changing
+        // the MAX. HDR also needs to work under manual brightness which never adjusts the
+        // brightness maximum; so we implement HDR-HBM in a way that doesn't adjust the max.
+        // See {@link #getHdrBrightnessValue}.
+        return !mIsHdrLayerPresent
+                && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
                 && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode);
     }
 
@@ -334,13 +379,13 @@
         // or if brightness is already in the high range, if there is any time left at all.
         final boolean isAllowedWithoutRestrictions = remainingTime >= mHbmData.timeMinMillis;
         final boolean isOnlyAllowedToStayOn = !isAllowedWithoutRestrictions
-                && remainingTime > 0 && mAutoBrightness > mHbmData.transitionPoint;
+                && remainingTime > 0 && mBrightness > mHbmData.transitionPoint;
         mIsTimeAvailable = isAllowedWithoutRestrictions || isOnlyAllowedToStayOn;
 
         // Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or
         // brightness change doesn't happen before then.
         long nextTimeout = -1;
-        if (mAutoBrightness > mHbmData.transitionPoint) {
+        if (mBrightness > mHbmData.transitionPoint) {
             // if we're in high-lux now, timeout when we run out of allowed time.
             nextTimeout = currentTime + remainingTime;
         } else if (!mIsTimeAvailable && mEvents.size() > 0) {
@@ -370,7 +415,7 @@
                     + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
                     + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
                     + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
-                    + ", brightness: " + mAutoBrightness
+                    + ", mBrightness: " + mBrightness
                     + ", RunningStartTimeMillis: " + mRunningStartTimeMillis
                     + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
                     + ", events: " + mEvents);
@@ -447,11 +492,12 @@
         public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
                 int maxW, int maxH, int flags) {
             mHandler.post(() -> {
-                mIsHdrLayerPresent = numberOfHdrLayers > 0;
-                // Calling the auto-brightness update so that we can recalculate
-                // auto-brightness with HDR in mind. When HDR layers are present,
-                // we don't limit auto-brightness' HBM time limits.
-                onAutoBrightnessChanged(mAutoBrightness);
+                mIsHdrLayerPresent = numberOfHdrLayers > 0
+                        && (float) (maxW * maxH)
+                                >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED);
+                // Calling the brightness update so that we can recalculate
+                // brightness with HDR in mind.
+                onBrightnessChanged(mBrightness);
             });
         }
     }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 2f17481..f953cc8 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -648,12 +648,11 @@
         public Runnable requestDisplayStateLocked(final int state, final float brightnessState,
                 final float sdrBrightnessState) {
             // Assume that the brightness is off if the display is being turned off.
-            assert state != Display.STATE_OFF || BrightnessSynchronizer.floatEquals(
-                    brightnessState, PowerManager.BRIGHTNESS_OFF_FLOAT);
+            assert state != Display.STATE_OFF
+                    || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
             final boolean stateChanged = (mState != state);
-            final boolean brightnessChanged =
-                    !(BrightnessSynchronizer.floatEquals(mBrightnessState, brightnessState)
-                    && BrightnessSynchronizer.floatEquals(mSdrBrightnessState, sdrBrightnessState));
+            final boolean brightnessChanged = mBrightnessState != brightnessState
+                    || mSdrBrightnessState != sdrBrightnessState;
             if (stateChanged || brightnessChanged) {
                 final long physicalDisplayId = mPhysicalDisplayId;
                 final IBinder token = getDisplayTokenLocked();
@@ -807,8 +806,7 @@
                     }
 
                     private float brightnessToBacklight(float brightness) {
-                        if (BrightnessSynchronizer.floatEquals(
-                                brightness, PowerManager.BRIGHTNESS_OFF_FLOAT)) {
+                        if (brightness == PowerManager.BRIGHTNESS_OFF_FLOAT) {
                             return PowerManager.BRIGHTNESS_OFF_FLOAT;
                         } else {
                             return getDisplayDeviceConfig().getBacklightFromBrightness(brightness);
diff --git a/services/core/java/com/android/server/display/RampAnimator.java b/services/core/java/com/android/server/display/RampAnimator.java
index 20feafa..ed3b15f 100644
--- a/services/core/java/com/android/server/display/RampAnimator.java
+++ b/services/core/java/com/android/server/display/RampAnimator.java
@@ -20,8 +20,6 @@
 import android.util.FloatProperty;
 import android.view.Choreographer;
 
-import com.android.internal.display.BrightnessSynchronizer;
-
 /**
  * A custom animator that progressively updates a property value at
  * a given variable rate until it reaches a particular target value.
@@ -157,10 +155,10 @@
             }
             final float oldCurrentValue = mCurrentValue;
             mCurrentValue = mAnimatedValue;
-            if (!BrightnessSynchronizer.floatEquals(oldCurrentValue, mCurrentValue)) {
+            if (oldCurrentValue != mCurrentValue) {
                 mProperty.setValue(mObject, mCurrentValue);
             }
-            if (!BrightnessSynchronizer.floatEquals(mTargetValue, mCurrentValue)) {
+            if (mTargetValue != mCurrentValue) {
                 postAnimationCallback();
             } else {
                 mAnimating = false;
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index 8787649..206682e 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1343,8 +1343,11 @@
         // it is handled (otherwise the wake lock would be leaked).
         mWakeLock.acquire(WAKELOCK_TIMEOUT_MILLIS);
         boolean success = mHandler.post(() -> {
-            runnable.run();
-            mWakeLock.release();
+            try {
+                runnable.run();
+            } finally {
+                mWakeLock.release();
+            }
         });
         if (!success) {
             mWakeLock.release();
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index 981e759..2519bbf 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -195,7 +195,7 @@
         }
 
         private String getSessionIdInternal(int userId) {
-            byte[] byteId = new byte[16]; // 128 bits
+            byte[] byteId = new byte[12]; // 96 bits (128 bits when expanded to Base64 string)
             mSecureRandom.nextBytes(byteId);
             String id = Base64.encodeToString(
                     byteId, Base64.NO_PADDING | Base64.NO_WRAP | Base64.URL_SAFE);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2634f6c..74ef4ca 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -6196,8 +6196,10 @@
         // Fix the notification as best we can.
         try {
             fixNotification(notification, pkg, tag, id, userId);
-
         } catch (Exception e) {
+            if (notification.isForegroundService()) {
+                throw new SecurityException("Invalid FGS notification", e);
+            }
             Slog.e(TAG, "Cannot fix notification", e);
             return;
         }
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index de45466..e28540c 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.Signature;
@@ -79,13 +80,13 @@
 
     /**
      * Allows opt-in to the latest targetSdkVersion enforced changes without changing target SDK.
-     * Turning this change off for an app targeting the latest SDK or higher is a no-op.
+     * Turning this change on for an app targeting the latest SDK or higher is a no-op.
      *
      * <p>Has no effect for apps using shared user id.
      *
      * TODO(b/143539591): Update description with relevant SELINUX changes this opts in to.
      */
-    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.R)
+    @Disabled
     @ChangeId
     static final long SELINUX_LATEST_CHANGES = 143539591L;
 
@@ -364,7 +365,8 @@
         }
         final ApplicationInfo appInfo = pkg.toAppInfoWithoutState();
         if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES, appInfo)) {
-            return Math.max(android.os.Build.VERSION_CODES.S, pkg.getTargetSdkVersion());
+            return Math.max(
+                    android.os.Build.VERSION_CODES.CUR_DEVELOPMENT, pkg.getTargetSdkVersion());
         } else if (compatibility.isChangeEnabledInternal(SELINUX_R_CHANGES, appInfo)) {
             return Math.max(android.os.Build.VERSION_CODES.R, pkg.getTargetSdkVersion());
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e6adeb3..fb4d96e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -904,7 +904,9 @@
             }
         } else {
             // handled by single key or another power key policy.
-            mSingleKeyGestureDetector.reset();
+            if (!mSingleKeyGestureDetector.isKeyIntercepted(KEYCODE_POWER)) {
+                mSingleKeyGestureDetector.reset();
+            }
         }
 
         finishPowerKeyPress();
@@ -918,7 +920,6 @@
     }
 
     private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
-        mCameraGestureTriggered = false;
         if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
             Slog.i(TAG, "Suppressed redundant power key press while "
                     + "already in the process of turning the screen on.");
@@ -1068,24 +1069,18 @@
     }
 
     private int getMaxMultiPressPowerCount() {
-        // GestureLauncherService could handle power multi tap gesture.
-        if (mGestureLauncherService != null
-                && mGestureLauncherService.isEmergencyGestureEnabled()) {
-            return 5; // EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD
-        }
-
+        // The actual max power button press count is 5
+        // (EMERGENCY_GESTURE_POWER_TAP_COUNT_THRESHOLD), which is coming from
+        // GestureLauncherService.
+        // To speed up the handling of single-press of power button inside SingleKeyGestureDetector,
+        // however, we limit the max count to the number of button presses actually handled by the
+        // SingleKeyGestureDetector.
         if (mTriplePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) {
             return 3;
         }
         if (mDoublePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) {
             return 2;
         }
-
-        if (mGestureLauncherService != null
-                && mGestureLauncherService.isCameraDoubleTapPowerEnabled()) {
-            return 2; // CAMERA_POWER_TAP_COUNT_THRESHOLD
-        }
-
         return 1;
     }
 
@@ -1972,7 +1967,6 @@
         void onPress(long downTime) {
             powerPress(downTime, 1 /*count*/,
                     mSingleKeyGestureDetector.beganFromNonInteractive());
-            finishPowerKeyPress();
         }
 
         @Override
@@ -1995,7 +1989,6 @@
         @Override
         void onMultiPress(long downTime, int count) {
             powerPress(downTime, count, mSingleKeyGestureDetector.beganFromNonInteractive());
-            finishPowerKeyPress();
         }
     }
 
@@ -3849,17 +3842,17 @@
         if (mGestureLauncherService == null) {
             return false;
         }
-
+        mCameraGestureTriggered = false;
         final MutableBoolean outLaunched = new MutableBoolean(false);
-        final boolean gesturedServiceIntercepted = mGestureLauncherService.interceptPowerKeyDown(
-                event, interactive, outLaunched);
-        if (outLaunched.value) {
-            mCameraGestureTriggered = true;
+        mGestureLauncherService.interceptPowerKeyDown(event, interactive, outLaunched);
+        if (!outLaunched.value) {
+            return false;
         }
-        if (outLaunched.value && mRequestedOrSleepingDefaultDisplay) {
+        mCameraGestureTriggered = true;
+        if (mRequestedOrSleepingDefaultDisplay) {
             mCameraGestureTriggeredDuringGoingToSleep = true;
         }
-        return gesturedServiceIntercepted;
+        return true;
     }
 
     /**
@@ -4232,7 +4225,8 @@
         mDefaultDisplayRotation.updateOrientationListener();
 
         if (mKeyguardDelegate != null) {
-            mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason, mCameraGestureTriggered);
+            mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason,
+                    mCameraGestureTriggeredDuringGoingToSleep);
         }
         if (mDisplayFoldController != null) {
             mDisplayFoldController.finishedGoingToSleep();
@@ -4428,6 +4422,8 @@
     // Called on the DisplayManager's DisplayPowerController thread.
     @Override
     public void screenTurnedOn(int displayId) {
+        if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turned on...");
+
         if (displayId != DEFAULT_DISPLAY) {
             return;
         }
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 3f4d920..1ef2bf9 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -272,8 +272,10 @@
                 if (DEBUG) {
                     Log.i(TAG, "press key " + KeyEvent.keyCodeToString(event.getKeyCode()));
                 }
-                mActiveRule.onPress(downTime);
-                reset();
+                Message msg = mHandler.obtainMessage(MSG_KEY_DELAYED_PRESS, mActiveRule.mKeyCode,
+                        1, downTime);
+                msg.setAsynchronous(true);
+                mHandler.sendMessage(msg);
                 return true;
             }
 
@@ -316,10 +318,7 @@
     }
 
     boolean isKeyIntercepted(int keyCode) {
-        if (mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode)) {
-            return mHandledByLongPress;
-        }
-        return false;
+        return mActiveRule != null && mActiveRule.shouldInterceptKey(keyCode);
     }
 
     boolean beganFromNonInteractive() {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index fb8498e..2a47512 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -1301,7 +1301,8 @@
     }
 
     /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
-    private final class ExternalVibratorService extends IExternalVibratorService.Stub {
+    @VisibleForTesting
+    final class ExternalVibratorService extends IExternalVibratorService.Stub {
         ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
 
         @Override
@@ -1332,6 +1333,7 @@
                 return vibHolder.scale;
             }
 
+            ExternalVibrationHolder cancelingExternalVibration = null;
             VibrationThread cancelingVibration = null;
             int scale;
             synchronized (mLock) {
@@ -1350,16 +1352,18 @@
                         cancelingVibration = mCurrentVibration;
                     }
                 } else {
+                    // At this point we have an externally controlled vibration playing already.
+                    // Since the interface defines that only one externally controlled vibration can
+                    // play at a time, we need to first mute the ongoing vibration and then return
+                    // a scale from this function for the new one. Ee can be assured that the
+                    // ongoing it will be muted in favor of the new vibration.
+                    //
+                    // Note that this doesn't support multiple concurrent external controls, as we
+                    // would need to mute the old one still if it came from a different controller.
+                    mCurrentExternalVibration.externalVibration.mute();
                     endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
+                    cancelingExternalVibration = mCurrentExternalVibration;
                 }
-                // At this point we either have an externally controlled vibration playing, or
-                // no vibration playing. Since the interface defines that only one externally
-                // controlled vibration can play at a time, by returning something other than
-                // SCALE_MUTE from this function we can be assured that if we are currently
-                // playing vibration, it will be muted in favor of the new vibration.
-                //
-                // Note that this doesn't support multiple concurrent external controls, as we
-                // would need to mute the old one still if it came from a different controller.
                 mCurrentExternalVibration = new ExternalVibrationHolder(vib);
                 mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
                 vib.linkToDeath(mCurrentExternalDeathRecipient);
@@ -1376,10 +1380,14 @@
                             + "external control", e);
                 }
             }
-            if (DEBUG) {
-                Slog.d(TAG, "Vibrator going under external control.");
+            if (cancelingExternalVibration == null) {
+                // We only need to set external control if it was not already set by another
+                // external vibration.
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibrator going under external control.");
+                }
+                setExternalControl(true);
             }
-            setExternalControl(true);
             if (DEBUG) {
                 Slog.e(TAG, "Playing external vibration: " + vib);
             }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 50c1c56c..a6ea585 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -551,6 +551,7 @@
                 if (maxFd > enableThreshold) {
                     // Do a manual GC to clean up fds that are hanging around as garbage.
                     System.gc();
+                    System.runFinalization();
                     maxFd = getMaxFd();
                 }
 
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 ffb1dd9..8336663 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appsearch/AppSearchConfigTest.java
@@ -54,6 +54,12 @@
                 AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES);
         assertThat(appSearchConfig.getCachedLimitConfigMaxDocumentCount()).isEqualTo(
                 AppSearchConfig.DEFAULT_LIMIT_CONFIG_MAX_DOCUMENT_COUNT);
+        assertThat(appSearchConfig.getCachedBytesOptimizeThreshold()).isEqualTo(
+                AppSearchConfig.DEFAULT_BYTES_OPTIMIZE_THRESHOLD);
+        assertThat(appSearchConfig.getCachedTimeOptimizeThresholdMs()).isEqualTo(
+                AppSearchConfig.DEFAULT_TIME_OPTIMIZE_THRESHOLD_MILLIS);
+        assertThat(appSearchConfig.getCachedDocCountOptimizeThreshold()).isEqualTo(
+                AppSearchConfig.DEFAULT_DOC_COUNT_OPTIMIZE_THRESHOLD);
     }
 
     @Test
@@ -163,10 +169,8 @@
 
     /**
      * Tests if we fall back to {@link AppSearchConfig#DEFAULT_SAMPLING_INTERVAL} if both default
-     * sampling
-     * interval and custom value are not set in DeviceConfig, and there is some other sampling
-     * interval
-     * set.
+     * sampling interval and custom value are not set in DeviceConfig, and there is some other
+     * sampling interval set.
      */
     @Test
     public void testFallbackToDefaultSamplingValue_useHardCodedDefault() {
@@ -269,7 +273,7 @@
     }
 
     @Test
-    public void testCustomizedValue() {
+    public void testCustomizedValue_maxDocument() {
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
                 AppSearchConfig.KEY_LIMIT_CONFIG_MAX_DOCUMENT_SIZE_BYTES,
                 Integer.toString(2001),
@@ -285,6 +289,64 @@
     }
 
     @Test
+    public void testCustomizedValue_optimizeThreshold() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_BYTES_OPTIMIZE_THRESHOLD,
+                Integer.toString(147147),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
+                Integer.toString(258258),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
+                Integer.toString(369369),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        assertThat(appSearchConfig.getCachedBytesOptimizeThreshold()).isEqualTo(147147);
+        assertThat(appSearchConfig.getCachedTimeOptimizeThresholdMs()).isEqualTo(258258);
+        assertThat(appSearchConfig.getCachedDocCountOptimizeThreshold()).isEqualTo(369369);
+    }
+
+    @Test
+    public void testCustomizedValueOverride_optimizeThreshold() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_BYTES_OPTIMIZE_THRESHOLD,
+                Integer.toString(147147),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
+                Integer.toString(258258),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
+                Integer.toString(369369),
+                false);
+
+        AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+
+        // Override
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_BYTES_OPTIMIZE_THRESHOLD,
+                Integer.toString(741741),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_TIME_OPTIMIZE_THRESHOLD_MILLIS,
+                Integer.toString(852852),
+                false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_APPSEARCH,
+                AppSearchConfig.KEY_DOC_COUNT_OPTIMIZE_THRESHOLD,
+                Integer.toString(963963),
+                false);
+
+        assertThat(appSearchConfig.getCachedBytesOptimizeThreshold()).isEqualTo(741741);
+        assertThat(appSearchConfig.getCachedTimeOptimizeThresholdMs()).isEqualTo(852852);
+        assertThat(appSearchConfig.getCachedDocCountOptimizeThreshold()).isEqualTo(963963);
+    }
+
+    @Test
     public void testNotUsable_afterClose() {
         AppSearchConfig appSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
 
@@ -302,5 +364,14 @@
         Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
                 IllegalStateException.class,
                 () -> appSearchConfig.getCachedSamplingIntervalForPutDocumentStats());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedBytesOptimizeThreshold());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedTimeOptimizeThresholdMs());
+        Assert.assertThrows("Trying to use a closed AppSearchConfig instance.",
+                IllegalStateException.class,
+                () -> appSearchConfig.getCachedDocCountOptimizeThreshold());
     }
 }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 4abc7ee..d1afa1a 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -39,6 +39,7 @@
         "services.usage",
         "services.uwb",
         "guava",
+        "guava-android-testlib",
         "androidx.test.core",
         "androidx.test.ext.truth",
         "androidx.test.runner",
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java b/services/tests/servicestests/src/com/android/server/appsearch/FrameworkOptimizeStrategyTest.java
similarity index 74%
rename from services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java
rename to services/tests/servicestests/src/com/android/server/appsearch/FrameworkOptimizeStrategyTest.java
index de71d21..8389c85 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/FrameworkOptimizeStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/FrameworkOptimizeStrategyTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -13,12 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.server.appsearch;
 
-package com.android.server.appsearch.external.localstorage;
-
-import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.BYTES_OPTIMIZE_THRESHOLD;
-import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.DOC_COUNT_OPTIMIZE_THRESHOLD;
-import static com.android.server.appsearch.external.localstorage.FrameworkOptimizeStrategy.TIME_OPTIMIZE_THRESHOLD_MILLIS;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -28,26 +25,17 @@
 import org.junit.Test;
 
 public class FrameworkOptimizeStrategyTest {
-    FrameworkOptimizeStrategy mFrameworkOptimizeStrategy = new FrameworkOptimizeStrategy();
-
-    @Test
-    public void testShouldOptimize_docCountThreshold() {
-        GetOptimizeInfoResultProto optimizeInfo =
-                GetOptimizeInfoResultProto.newBuilder()
-                        .setTimeSinceLastOptimizeMs(0)
-                        .setEstimatedOptimizableBytes(BYTES_OPTIMIZE_THRESHOLD)
-                        .setOptimizableDocs(0)
-                        .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build())
-                        .build();
-        assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue();
-    }
+    AppSearchConfig mAppSearchConfig = AppSearchConfig.create(DIRECT_EXECUTOR);
+    FrameworkOptimizeStrategy mFrameworkOptimizeStrategy =
+            new FrameworkOptimizeStrategy(mAppSearchConfig);
 
     @Test
     public void testShouldOptimize_byteThreshold() {
         GetOptimizeInfoResultProto optimizeInfo =
                 GetOptimizeInfoResultProto.newBuilder()
-                        .setTimeSinceLastOptimizeMs(TIME_OPTIMIZE_THRESHOLD_MILLIS)
-                        .setEstimatedOptimizableBytes(0)
+                        .setTimeSinceLastOptimizeMs(0)
+                        .setEstimatedOptimizableBytes(
+                                mAppSearchConfig.getCachedBytesOptimizeThreshold())
                         .setOptimizableDocs(0)
                         .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build())
                         .build();
@@ -58,9 +46,23 @@
     public void testShouldNotOptimize_timeThreshold() {
         GetOptimizeInfoResultProto optimizeInfo =
                 GetOptimizeInfoResultProto.newBuilder()
+                        .setTimeSinceLastOptimizeMs(
+                                mAppSearchConfig.getCachedTimeOptimizeThresholdMs())
+                        .setEstimatedOptimizableBytes(0)
+                        .setOptimizableDocs(0)
+                        .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build())
+                        .build();
+        assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue();
+    }
+
+    @Test
+    public void testShouldOptimize_docCountThreshold() {
+        GetOptimizeInfoResultProto optimizeInfo =
+                GetOptimizeInfoResultProto.newBuilder()
                         .setTimeSinceLastOptimizeMs(0)
                         .setEstimatedOptimizableBytes(0)
-                        .setOptimizableDocs(DOC_COUNT_OPTIMIZE_THRESHOLD)
+                        .setOptimizableDocs(
+                                mAppSearchConfig.getCachedDocCountOptimizeThreshold())
                         .setStatus(StatusProto.newBuilder().setCode(StatusProto.Code.OK).build())
                         .build();
         assertThat(mFrameworkOptimizeStrategy.shouldOptimize(optimizeInfo)).isTrue();
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 fbcf53d..7243947 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -74,6 +74,9 @@
     private static final float DEFAULT_MIN = 0.01f;
     private static final float DEFAULT_MAX = 0.80f;
 
+    private static final int DISPLAY_WIDTH = 900;
+    private static final int DISPLAY_HEIGHT = 1600;
+
     private static final float EPSILON = 0.000001f;
 
     private OffsettableClock mClock;
@@ -90,6 +93,8 @@
 
     @Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
 
+    @Mock private BrightnessSetting mBrightnessSetting;
+
     private static final HighBrightnessModeData DEFAULT_HBM_DATA =
             new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
                     TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
@@ -98,6 +103,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mClock = new OffsettableClock.Stopped();
+        mTestLooper = new TestLooper(mClock::now);
         mDisplayToken = null;
         mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
@@ -114,8 +121,8 @@
     public void testNoHbmData() {
         initHandler(null);
         final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null,
-                () -> {}, mContextSpy);
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
+                DEFAULT_MAX, null, () -> {}, mContextSpy, mBrightnessSetting);
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
     }
 
@@ -123,8 +130,8 @@
     public void testNoHbmData_Enabled() {
         initHandler(null);
         final HighBrightnessModeController hbmc = new HighBrightnessModeController(
-                mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, null,
-                () -> {}, mContextSpy);
+                mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
+                DEFAULT_MAX, null, () -> {}, mContextSpy, mBrightnessSetting);
         hbmc.setAutoBrightnessEnabled(true);
         hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
@@ -180,7 +187,7 @@
 
         hbmc.setAutoBrightnessEnabled(true);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
 
         // Verify we are in HBM
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -212,7 +219,7 @@
 
         hbmc.setAutoBrightnessEnabled(true);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
 
         // Verify we are in HBM
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -237,18 +244,18 @@
         hbmc.setAutoBrightnessEnabled(true);
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
 
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
         advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
 
         // Verify we are in HBM
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
 
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT - 0.01f);
+        hbmc.onBrightnessChanged(TRANSITION_POINT - 0.01f);
         advanceTime(1);
 
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
 
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
         advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
 
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -267,13 +274,13 @@
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
 
         // Go into HBM for half the allowed window
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 0.01f);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 0.01f);
         advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2);
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
 
         // Move lux below threshold (ending first event);
         hbmc.onAmbientLuxChange(MINIMUM_LUX - 1);
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT);
+        hbmc.onBrightnessChanged(TRANSITION_POINT);
         assertState(hbmc, DEFAULT_MIN, TRANSITION_POINT, HIGH_BRIGHTNESS_MODE_OFF);
 
         // Move up some amount of time so that there's still time in the window even after a
@@ -283,7 +290,7 @@
 
         // Go into HBM for just under the second half of allowed window
         hbmc.onAmbientLuxChange(MINIMUM_LUX + 1);
-        hbmc.onAutoBrightnessChanged(TRANSITION_POINT + 1);
+        hbmc.onBrightnessChanged(TRANSITION_POINT + 1);
         advanceTime((TIME_ALLOWED_IN_WINDOW_MILLIS / 2) - 1);
 
         assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_SUNLIGHT);
@@ -355,8 +362,9 @@
     // Creates instance with standard initialization values.
     private HighBrightnessModeController createDefaultHbm(OffsettableClock clock) {
         initHandler(clock);
-        return new HighBrightnessModeController(mInjectorMock, mHandler, mDisplayToken, DEFAULT_MIN,
-                DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {}, mContextSpy);
+        return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH,
+                DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN, DEFAULT_MAX, DEFAULT_HBM_DATA, () -> {},
+                mContextSpy, mBrightnessSetting);
     }
 
     private void initHandler(OffsettableClock clock) {
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
index 6109c2f..6c2db36 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java
@@ -46,6 +46,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.google.common.testing.EqualsTester;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -208,6 +210,24 @@
     }
 
     @Test
+    public void testEqualsActiveSource() {
+        int logicalAddress = 0;
+        int physicalAddress = 0x0000;
+        new EqualsTester()
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress, physicalAddress),
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress, physicalAddress))
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress, physicalAddress + 1))
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(logicalAddress + 1, physicalAddress))
+                .addEqualityGroup(
+                        new HdmiCecLocalDevice.ActiveSource(
+                                logicalAddress + 1, physicalAddress + 1))
+                .testEquals();
+    }
+
+    @Test
     public void dispatchMessage_logicalAddressDoesNotMatch() {
         HdmiCecMessage msg =
                 new HdmiCecMessage(
diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
index cee4cda..901b200 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
@@ -44,7 +44,7 @@
 public class SELinuxMMACTest {
 
     private static final String PACKAGE_NAME = "my.package";
-    private static final int LATEST_OPT_IN_VERSION = Build.VERSION_CODES.S;
+    private static final int LATEST_OPT_IN_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT;
     private static final int R_OPT_IN_VERSION = Build.VERSION_CODES.R;
 
     @Mock
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 0449e44..75f8a44 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -46,6 +46,7 @@
     private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
     private final List<Integer> mBraking = new ArrayList<>();
     private final List<Float> mAmplitudes = new ArrayList<>();
+    private final List<Boolean> mExternalControlStates = new ArrayList<>();
     private final Handler mHandler;
     private final FakeNativeWrapper mNativeWrapper;
 
@@ -139,6 +140,7 @@
 
         @Override
         public void setExternalControl(boolean enabled) {
+            mExternalControlStates.add(enabled);
         }
 
         @Override
@@ -301,6 +303,11 @@
         return new ArrayList<>(mEffectSegments);
     }
 
+    /** Return list of states set for external control to the fake vibrator hardware. */
+    public List<Boolean> getExternalControlStates() {
+        return mExternalControlStates;
+    }
+
     /**
      * Return the {@link PrebakedSegment} effect enabled with given id, or {@code null} if
      * missing or disabled.
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 77003b2..5a00e0d 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
@@ -50,8 +51,11 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.os.CombinedVibration;
+import android.os.ExternalVibration;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IExternalVibrationController;
+import android.os.IExternalVibratorService;
 import android.os.IVibratorStateListener;
 import android.os.Looper;
 import android.os.PowerManager;
@@ -108,6 +112,8 @@
     private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build();
     private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder()
             .setBatterySaverEnabled(true).build();
+    private static final AudioAttributes AUDIO_ATTRS =
+            new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_ALARM).build();
     private static final VibrationAttributes ALARM_ATTRS =
             new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build();
     private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS =
@@ -136,6 +142,7 @@
     private TestLooper mTestLooper;
     private FakeVibrator mVibrator;
     private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener;
+    private VibratorManagerService.ExternalVibratorService mExternalVibratorService;
 
     @Before
     public void setUp() throws Exception {
@@ -208,6 +215,9 @@
 
                     @Override
                     void addService(String name, IBinder service) {
+                        Object serviceInstance = service;
+                        mExternalVibratorService =
+                                (VibratorManagerService.ExternalVibratorService) serviceInstance;
                     }
                 });
     }
@@ -967,6 +977,69 @@
         assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
     }
 
+    @Test
+    public void onExternalVibration_setsExternalControl() {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
+                mock(IExternalVibrationController.class));
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        mExternalVibratorService.onExternalVibrationStop(externalVibration);
+
+        assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+        assertEquals(Arrays.asList(true, false),
+                mVibratorProviders.get(1).getExternalControlStates());
+    }
+
+    @Test
+    public void onExternalVibration_withOngoingExternalVibration_mutesPreviousVibration()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        createSystemReadyService();
+
+        IExternalVibrationController firstController = mock(IExternalVibrationController.class);
+        IExternalVibrationController secondController = mock(IExternalVibrationController.class);
+        ExternalVibration firstVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
+                firstController);
+        int firstScale = mExternalVibratorService.onExternalVibrationStart(firstVibration);
+
+        ExternalVibration secondVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
+                secondController);
+        int secondScale = mExternalVibratorService.onExternalVibrationStart(secondVibration);
+
+        assertEquals(IExternalVibratorService.SCALE_NONE, firstScale);
+        assertEquals(IExternalVibratorService.SCALE_NONE, secondScale);
+        verify(firstController).mute();
+        verifyNoMoreInteractions(secondController);
+        // Set external control called only once.
+        assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates());
+    }
+
+    @Test
+    public void onExternalVibration_withOngoingVibration_cancelsOngoingVibrationImmediately()
+            throws Exception {
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
+                IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+
+        VibrationEffect effect = VibrationEffect.createOneShot(10 * TEST_TIMEOUT_MILLIS, 100);
+        vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+        assertTrue(waitUntil(s -> s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, AUDIO_ATTRS,
+                mock(IExternalVibrationController.class));
+        int scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertEquals(IExternalVibratorService.SCALE_NONE, scale);
+
+        // Vibration is cancelled.
+        assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
+        assertEquals(Arrays.asList(true), mVibratorProviders.get(1).getExternalControlStates());
+    }
+
     private VibrationEffectSegment expectedPrebaked(int effectId) {
         return new PrebakedSegment(effectId, false, VibrationEffect.EFFECT_STRENGTH_MEDIUM);
     }