Merge "Refine dump info of One Handed Mode components" into sc-dev
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 5def55f..5bf0b84 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -33,9 +33,10 @@
 
 java_sdk_library {
     name: "framework-appsearch",
-    srcs: [ ":framework-appsearch-sources" ],
+    srcs: [":framework-appsearch-sources"],
     sdk_version: "core_platform", // TODO(b/146218515) should be module_current
     impl_only_libs: ["framework-minus-apex"], // TODO(b/146218515) should be removed
+    libs: ["unsupportedappusage"], // TODO(b/181887768) should be removed
     defaults: ["framework-module-defaults"],
     permitted_packages: ["android.app.appsearch"],
     aidl: {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index 64ac63c..c112d0e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -21,6 +21,7 @@
 import android.annotation.UserIdInt;
 import android.app.appsearch.exceptions.AppSearchException;
 import android.app.appsearch.util.SchemaMigrationUtil;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -295,6 +296,19 @@
     }
 
     /**
+     * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void getByUri(
+            @NonNull GetByUriRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BatchResultCallback<String, GenericDocument> callback) {
+        getByDocumentId(request.toGetByDocumentIdRequest(), executor, callback);
+    }
+
+    /**
      * Gets {@link GenericDocument} objects by document IDs in a namespace from the {@link
      * AppSearchSession} database.
      *
@@ -489,6 +503,19 @@
     }
 
     /**
+     * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void remove(
+            @NonNull RemoveByUriRequest request,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BatchResultCallback<String, Void> callback) {
+        remove(request.toRemoveByDocumentIdRequest(), executor, callback);
+    }
+
+    /**
      * Removes {@link GenericDocument} objects by document IDs in a namespace from the {@link
      * AppSearchSession} database.
      *
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
index 2a941fb..4378a98 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.app.appsearch.exceptions.IllegalSchemaException;
 import android.app.appsearch.util.BundleUtil;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.util.ArraySet;
 
@@ -747,6 +748,31 @@
             }
 
             /**
+             * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+             * @hide
+             */
+            @Deprecated
+            @UnsupportedAppUsage
+            public Builder(@NonNull String propertyName) {
+                mBundle.putString(NAME_FIELD, propertyName);
+                mBundle.putInt(DATA_TYPE_FIELD, DATA_TYPE_DOCUMENT);
+                mBundle.putInt(CARDINALITY_FIELD, CARDINALITY_OPTIONAL);
+                mBundle.putBoolean(INDEX_NESTED_PROPERTIES_FIELD, false);
+            }
+
+            /**
+             * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+             * @hide
+             */
+            @Deprecated
+            @UnsupportedAppUsage
+            @NonNull
+            public Builder setSchemaType(@NonNull String schemaType) {
+                mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
+                return this;
+            }
+
+            /**
              * The cardinality of the property (whether it is optional, required or repeated).
              *
              * <p>If this method is not called, the default cardinality is {@link
@@ -778,6 +804,18 @@
             }
 
             /**
+             * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+             * @hide
+             */
+            @Deprecated
+            @UnsupportedAppUsage
+            @NonNull
+            public DocumentPropertyConfig.Builder setIndexNestedProperties(
+                    boolean indexNestedProperties) {
+                return setShouldIndexNestedProperties(indexNestedProperties);
+            }
+
+            /**
              * Constructs a new {@link PropertyConfig} from the contents of this builder.
              *
              * <p>After calling this method, the builder must no longer be used.
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 2e42749..39a4884 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.appsearch.util.BundleUtil;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.util.Log;
@@ -137,6 +138,17 @@
         return mBundle;
     }
 
+    /**
+     * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    @NonNull
+    public String getUri() {
+        return getId();
+    }
+
     /** Returns the unique identifier of the {@link GenericDocument}. */
     @NonNull
     public String getId() {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
new file mode 100644
index 0000000..7b05eac
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+@Deprecated
+public final class GetByUriRequest {
+    /**
+     * Schema type to be used in {@link GetByUriRequest.Builder#addProjection} to apply property
+     * paths to all results, excepting any types that have had their own, specific property paths
+     * set.
+     */
+    public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*";
+
+    private final String mNamespace;
+    private final Set<String> mIds;
+    private final Map<String, List<String>> mTypePropertyPathsMap;
+
+    GetByUriRequest(
+            @NonNull String namespace,
+            @NonNull Set<String> ids,
+            @NonNull Map<String, List<String>> typePropertyPathsMap) {
+        mNamespace = Objects.requireNonNull(namespace);
+        mIds = Objects.requireNonNull(ids);
+        mTypePropertyPathsMap = Objects.requireNonNull(typePropertyPathsMap);
+    }
+
+    /** Returns the namespace attached to the request. */
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    /** Returns the set of document IDs attached to the request. */
+    @NonNull
+    public Set<String> getUris() {
+        return Collections.unmodifiableSet(mIds);
+    }
+
+    /**
+     * Returns a map from schema type to property paths to be used for projection.
+     *
+     * <p>If the map is empty, then all properties will be retrieved for all results.
+     *
+     * <p>Calling this function repeatedly is inefficient. Prefer to retain the Map returned by this
+     * function, rather than calling it multiple times.
+     */
+    @NonNull
+    public Map<String, List<String>> getProjections() {
+        Map<String, List<String>> copy = new ArrayMap<>();
+        for (Map.Entry<String, List<String>> entry : mTypePropertyPathsMap.entrySet()) {
+            copy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
+        }
+        return copy;
+    }
+
+    /**
+     * Returns a map from schema type to property paths to be used for projection.
+     *
+     * <p>If the map is empty, then all properties will be retrieved for all results.
+     *
+     * <p>A more efficient version of {@link #getProjections}, but it returns a modifiable map. This
+     * is not meant to be unhidden and should only be used by internal classes.
+     *
+     * @hide
+     */
+    @NonNull
+    public Map<String, List<String>> getProjectionsInternal() {
+        return mTypePropertyPathsMap;
+    }
+
+    /**
+     * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+     * @hide
+     */
+    @Deprecated
+    @NonNull
+    public GetByDocumentIdRequest toGetByDocumentIdRequest() {
+        GetByDocumentIdRequest.Builder builder =
+                new GetByDocumentIdRequest.Builder(mNamespace).addIds(mIds);
+        for (Map.Entry<String, List<String>> projection : mTypePropertyPathsMap.entrySet()) {
+            builder.addProjection(projection.getKey(), projection.getValue());
+        }
+        return builder.build();
+    }
+
+    /**
+     * Builder for {@link GetByUriRequest} objects.
+     *
+     * <p>Once {@link #build} is called, the instance can no longer be used.
+     */
+    public static final class Builder {
+        private final String mNamespace;
+        private final Set<String> mIds = new ArraySet<>();
+        private final Map<String, List<String>> mProjectionTypePropertyPaths = new ArrayMap<>();
+        private boolean mBuilt = false;
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        public Builder(@NonNull String namespace) {
+            mNamespace = Objects.requireNonNull(namespace);
+        }
+
+        /**
+         * Adds one or more document IDs to the request.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
+        @NonNull
+        public Builder addUris(@NonNull String... ids) {
+            Objects.requireNonNull(ids);
+            return addUris(Arrays.asList(ids));
+        }
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public Builder addUris(@NonNull Collection<String> ids) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Objects.requireNonNull(ids);
+            mIds.addAll(ids);
+            return this;
+        }
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public Builder addProjection(
+                @NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Objects.requireNonNull(schemaType);
+            Objects.requireNonNull(propertyPaths);
+            List<String> propertyPathsList = new ArrayList<>(propertyPaths.size());
+            for (String propertyPath : propertyPaths) {
+                Objects.requireNonNull(propertyPath);
+                propertyPathsList.add(propertyPath);
+            }
+            mProjectionTypePropertyPaths.put(schemaType, propertyPathsList);
+            return this;
+        }
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public GetByUriRequest build() {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBuilt = true;
+            return new GetByUriRequest(mNamespace, mIds, mProjectionTypePropertyPaths);
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
new file mode 100644
index 0000000..9c74966
--- /dev/null
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appsearch;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.util.ArraySet;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+ * @hide
+ */
+@Deprecated
+public final class RemoveByUriRequest {
+    private final String mNamespace;
+    private final Set<String> mIds;
+
+    RemoveByUriRequest(String namespace, Set<String> ids) {
+        mNamespace = namespace;
+        mIds = ids;
+    }
+
+    /** Returns the namespace to remove documents from. */
+    @NonNull
+    public String getNamespace() {
+        return mNamespace;
+    }
+
+    /** Returns the set of document IDs attached to the request. */
+    @NonNull
+    public Set<String> getUris() {
+        return Collections.unmodifiableSet(mIds);
+    }
+
+    /**
+     * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+     * @hide
+     */
+    @Deprecated
+    @NonNull
+    public RemoveByDocumentIdRequest toRemoveByDocumentIdRequest() {
+        return new RemoveByDocumentIdRequest.Builder(mNamespace).addIds(mIds).build();
+    }
+
+    /**
+     * Builder for {@link RemoveByUriRequest} objects.
+     *
+     * <p>Once {@link #build} is called, the instance can no longer be used.
+     */
+    public static final class Builder {
+        private final String mNamespace;
+        private final Set<String> mIds = new ArraySet<>();
+        private boolean mBuilt = false;
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        public Builder(@NonNull String namespace) {
+            mNamespace = Objects.requireNonNull(namespace);
+        }
+
+        /**
+         * Adds one or more document IDs to the request.
+         *
+         * @throws IllegalStateException if the builder has already been used.
+         */
+        @NonNull
+        public Builder addUris(@NonNull String... ids) {
+            Objects.requireNonNull(ids);
+            return addUris(Arrays.asList(ids));
+        }
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public Builder addUris(@NonNull Collection<String> ids) {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            Objects.requireNonNull(ids);
+            mIds.addAll(ids);
+            return this;
+        }
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public RemoveByUriRequest build() {
+            Preconditions.checkState(!mBuilt, "Builder has already been used");
+            mBuilt = true;
+            return new RemoveByUriRequest(mNamespace, mIds);
+        }
+    }
+}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
index 8c8ade8..5cb59b3 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import com.android.internal.util.Preconditions;
 
@@ -68,7 +69,8 @@
     /** Builder for {@link ReportUsageRequest} objects. */
     public static final class Builder {
         private final String mNamespace;
-        private final String mDocumentId;
+        // TODO(b/181887768): Make this final
+        private String mDocumentId;
         private Long mUsageTimestampMillis;
         private boolean mBuilt = false;
 
@@ -79,6 +81,40 @@
         }
 
         /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        public Builder(@NonNull String namespace) {
+            mNamespace = Objects.requireNonNull(namespace);
+        }
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public Builder setUri(@NonNull String uri) {
+            mDocumentId = uri;
+            return this;
+        }
+
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public ReportUsageRequest.Builder setUsageTimeMillis(
+                @CurrentTimeMillisLong long usageTimestampMillis) {
+            return setUsageTimestampMillis(usageTimestampMillis);
+        }
+
+        /**
          * Sets the timestamp in milliseconds of the usage report (the time at which the document
          * was used).
          *
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
index b648071..9a1796c 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -192,7 +192,7 @@
             return this;
         }
 
-        /** @deprecated this method exists only for dogfooder transition and must be removed */
+        /** @deprecated This method exists only for dogfooder transition and must be removed. */
         @Deprecated
         @NonNull
         public Builder addMatch(@NonNull MatchInfo matchInfo) {
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index b7bd387..7ad5fe8 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.util.ArraySet;
 
@@ -324,6 +325,17 @@
             return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
         }
 
+        /**
+         * @deprecated TODO(b/181887768): Exists for dogfood transition; must be removed.
+         * @hide
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        @NonNull
+        public String getUri() {
+            return getDocumentId();
+        }
+
         /** Returns the id of the {@link GenericDocument} that failed to be migrated. */
         @NonNull
         public String getDocumentId() {
diff --git a/core/api/current.txt b/core/api/current.txt
index f23d24a..b257f5e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -586,7 +586,6 @@
     field public static final int dropDownWidth = 16843362; // 0x1010262
     field public static final int duplicateParentState = 16842985; // 0x10100e9
     field public static final int duration = 16843160; // 0x1010198
-    field public static final int edgeEffectType;
     field public static final int editTextBackground = 16843602; // 0x1010352
     field public static final int editTextColor = 16843601; // 0x1010351
     field public static final int editTextPreferenceStyle = 16842898; // 0x1010092
@@ -12582,7 +12581,7 @@
     method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
     method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
     method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@Nullable String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryProviderProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryReceiverProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryServiceProperty(@NonNull String);
@@ -53791,7 +53790,6 @@
     method public int getCheckedItemPosition();
     method public android.util.SparseBooleanArray getCheckedItemPositions();
     method public int getChoiceMode();
-    method public int getEdgeEffectType();
     method public int getListPaddingBottom();
     method public int getListPaddingLeft();
     method public int getListPaddingRight();
@@ -53833,7 +53831,6 @@
     method public void setChoiceMode(int);
     method public void setDrawSelectorOnTop(boolean);
     method public void setEdgeEffectColor(@ColorInt int);
-    method public void setEdgeEffectType(int);
     method public void setFastScrollAlwaysVisible(boolean);
     method public void setFastScrollEnabled(boolean);
     method public void setFastScrollStyle(int);
@@ -54524,7 +54521,6 @@
     method @ColorInt public int getColor();
     method public float getDistance();
     method public int getMaxHeight();
-    method public int getType();
     method public boolean isFinished();
     method public void onAbsorb(int);
     method public void onPull(float);
@@ -54534,10 +54530,7 @@
     method public void setBlendMode(@Nullable android.graphics.BlendMode);
     method public void setColor(@ColorInt int);
     method public void setSize(int, int);
-    method public void setType(int);
     field public static final android.graphics.BlendMode DEFAULT_BLEND_MODE;
-    field public static final int TYPE_GLOW = 0; // 0x0
-    field public static final int TYPE_STRETCH = 1; // 0x1
   }
 
   public class EditText extends android.widget.TextView {
@@ -54842,7 +54835,6 @@
     method public boolean executeKeyEvent(android.view.KeyEvent);
     method public void fling(int);
     method public boolean fullScroll(int);
-    method public int getEdgeEffectType();
     method @ColorInt public int getLeftEdgeEffectColor();
     method public int getMaxScrollAmount();
     method @ColorInt public int getRightEdgeEffectColor();
@@ -54850,7 +54842,6 @@
     method public boolean isSmoothScrollingEnabled();
     method public boolean pageScroll(int);
     method public void setEdgeEffectColor(@ColorInt int);
-    method public void setEdgeEffectType(int);
     method public void setFillViewport(boolean);
     method public void setLeftEdgeEffectColor(@ColorInt int);
     method public void setRightEdgeEffectColor(@ColorInt int);
@@ -55736,7 +55727,6 @@
     method public void fling(int);
     method public boolean fullScroll(int);
     method @ColorInt public int getBottomEdgeEffectColor();
-    method public int getEdgeEffectType();
     method public int getMaxScrollAmount();
     method @ColorInt public int getTopEdgeEffectColor();
     method public boolean isFillViewport();
@@ -55745,7 +55735,6 @@
     method public void scrollToDescendant(@NonNull android.view.View);
     method public void setBottomEdgeEffectColor(@ColorInt int);
     method public void setEdgeEffectColor(@ColorInt int);
-    method public void setEdgeEffectType(int);
     method public void setFillViewport(boolean);
     method public void setSmoothScrollingEnabled(boolean);
     method public void setTopEdgeEffectColor(@ColorInt int);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b3e656d..ff210e1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5613,7 +5613,7 @@
     }
 
     /** Performs the activity relaunch locally vs. requesting from system-server. */
-    private void handleRelaunchActivityLocally(IBinder token) {
+    public void handleRelaunchActivityLocally(IBinder token) {
         final ActivityClientRecord r = mActivities.get(token);
         if (r == null) {
             Log.w(TAG, "Activity to relaunch no longer exists");
@@ -5977,20 +5977,6 @@
             // Update all affected Resources objects to use new ResourcesImpl
             mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs);
         }
-
-        ApplicationPackageManager.configurationChanged();
-
-        // Trigger a regular Configuration change event, only with a different assetsSeq number
-        // so that we actually call through to all components.
-        // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to
-        // store configurations per-process.
-        final Configuration config = mConfigurationController.getConfiguration();
-        Configuration newConfig = new Configuration();
-        newConfig.assetsSeq = (config != null ? config.assetsSeq : 0) + 1;
-        mConfigurationController.handleConfigurationChanged(newConfig, null /* compat */);
-
-        // Preserve windows to avoid black flickers when overlays change.
-        relaunchAllActivities(true /* preserveWindows */, "handleApplicationInfoChanged");
     }
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d77ce4a..efeef1b 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1894,7 +1894,7 @@
             OP_MANAGE_MEDIA,                    // MANAGE_MEDIA
             OP_BLUETOOTH_CONNECT,               // OP_BLUETOOTH_CONNECT
             OP_UWB_RANGING,                     // OP_UWB_RANGING
-            OP_ACTIVITY_RECOGNITION_SOURCE,     // OP_ACTIVITY_RECOGNITION_SOURCE
+            OP_ACTIVITY_RECOGNITION,            // OP_ACTIVITY_RECOGNITION_SOURCE
             OP_BLUETOOTH_ADVERTISE,             // OP_BLUETOOTH_ADVERTISE
             OP_RECORD_INCOMING_PHONE_AUDIO,     // OP_RECORD_INCOMING_PHONE_AUDIO
     };
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f07f453..7dc662a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -1847,6 +1847,8 @@
      * Delegation of certificate installation and management. This scope grants access to the
      * {@link #getInstalledCaCerts}, {@link #hasCaCertInstalled}, {@link #installCaCert},
      * {@link #uninstallCaCert}, {@link #uninstallAllUserCaCerts} and {@link #installKeyPair} APIs.
+     * This scope also grants the ability to read identifiers that the delegating device owner or
+     * profile owner can obtain. See {@link #getEnrollmentSpecificId()}.
      */
     public static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
 
@@ -13620,12 +13622,20 @@
      * It is available both in a work profile and on a fully-managed device.
      * The identifier would be consistent even if the work profile is removed and enrolled again
      * (to the same organization), or the device is factory reset and re-enrolled.
-
+     *
      * Can only be called by the Profile Owner or Device Owner, if the
      * {@link #setOrganizationId(String)} was previously called.
      * If {@link #setOrganizationId(String)} was not called, then the returned value will be an
      * empty string.
      *
+     * <p>Note about access to device identifiers: a device owner, a profile owner of an
+     * organization-owned device or the delegated certificate installer (holding the
+     * {@link #DELEGATION_CERT_INSTALL} delegation) on such a device can still obtain hardware
+     * identifiers by calling e.g. {@link android.os.Build#getSerial()}, in addition to using
+     * this method. However, a profile owner on a personal (non organization-owned) device, or the
+     * delegated certificate installer on such a device, cannot obtain hardware identifiers anymore
+     * and must switch to using this method.
+     *
      * @return A stable, enrollment-specific identifier.
      * @throws SecurityException if the caller is not a profile owner or device owner.
      */
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 99bbcde..07d8478 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4605,7 +4605,7 @@
      * Query for all of the permissions associated with a particular group.
      *
      * @param permissionGroup The fully qualified name (i.e. com.google.permission.LOGIN)
-     *            of the permission group you are interested in. Use null to
+     *            of the permission group you are interested in. Use {@code null} to
      *            find all of the permissions not associated with a group.
      * @param flags Additional option flags to modify the data returned.
      * @return Returns a list of {@link PermissionInfo} containing information
@@ -4615,7 +4615,7 @@
      */
     //@Deprecated
     @NonNull
-    public abstract List<PermissionInfo> queryPermissionsByGroup(@NonNull String permissionGroup,
+    public abstract List<PermissionInfo> queryPermissionsByGroup(@Nullable String permissionGroup,
             @PermissionInfoFlags int flags) throws NameNotFoundException;
 
     /**
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 22d75ef..725576f 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -3069,6 +3069,13 @@
     /**
      * @hide
      */
+    public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) {
+        sCompatibilityModeEnabled = compatibilityModeEnabled;
+    }
+
+    /**
+     * @hide
+     */
     public static void readConfigUseRoundIcon(Resources r) {
         if (r != null) {
             sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index 250145e..52dad3e 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -24,7 +24,10 @@
 import android.annotation.TestApi;
 import android.content.Context;
 
+import com.android.internal.util.ArrayUtils;
+
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Manages the state of the system for devices with user-configurable hardware like a foldable
@@ -170,4 +173,34 @@
          */
         void onStateChanged(int state);
     }
+
+    /**
+     * Listens to changes in device state and reports the state as folded if the device state
+     * matches the value in the {@link com.android.internal.R.integer.config_foldedDeviceState}
+     * resource.
+     * @hide
+     */
+    public static class FoldStateListener implements DeviceStateCallback {
+        private final int[] mFoldedDeviceStates;
+        private final Consumer<Boolean> mDelegate;
+
+        @Nullable
+        private Boolean lastResult;
+
+        public FoldStateListener(Context context, Consumer<Boolean> listener) {
+            mFoldedDeviceStates = context.getResources().getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates);
+            mDelegate = listener;
+        }
+
+        @Override
+        public final void onStateChanged(int state) {
+            final boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state);
+
+            if (lastResult == null || !lastResult.equals(folded)) {
+                lastResult = folded;
+                mDelegate.accept(folded);
+            }
+        }
+    }
 }
diff --git a/core/java/android/os/AggregateBatteryConsumer.java b/core/java/android/os/AggregateBatteryConsumer.java
new file mode 100644
index 0000000..449e3ae
--- /dev/null
+++ b/core/java/android/os/AggregateBatteryConsumer.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.NonNull;
+
+/**
+ * Contains power consumption data across the entire device.
+ *
+ * {@hide}
+ */
+public final class AggregateBatteryConsumer extends BatteryConsumer implements Parcelable {
+
+    private final double mConsumedPowerMah;
+
+    public AggregateBatteryConsumer(@NonNull Builder builder) {
+        super(builder.mPowerComponentsBuilder.build());
+        mConsumedPowerMah = builder.mConsumedPowerMah;
+    }
+
+    private AggregateBatteryConsumer(@NonNull Parcel source) {
+        super(new PowerComponents(source));
+        mConsumedPowerMah = source.readDouble();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeDouble(mConsumedPowerMah);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Creator<AggregateBatteryConsumer> CREATOR =
+            new Creator<AggregateBatteryConsumer>() {
+                public AggregateBatteryConsumer createFromParcel(@NonNull Parcel source) {
+                    return new AggregateBatteryConsumer(source);
+                }
+
+                public AggregateBatteryConsumer[] newArray(int size) {
+                    return new AggregateBatteryConsumer[size];
+                }
+            };
+
+    @Override
+    public double getConsumedPower() {
+        return mConsumedPowerMah;
+    }
+
+    /**
+     * Builder for DeviceBatteryConsumer.
+     */
+    public static final class Builder extends BaseBuilder<AggregateBatteryConsumer.Builder> {
+        private double mConsumedPowerMah;
+
+        public Builder(@NonNull String[] customPowerComponentNames, boolean includePowerModels) {
+            super(customPowerComponentNames, includePowerModels);
+        }
+
+        /**
+         * Sets the total power included in this aggregate.
+         */
+        public Builder setConsumedPower(double consumedPowerMah) {
+            mConsumedPowerMah = consumedPowerMah;
+            return this;
+        }
+
+        /**
+         * Creates a read-only object out of the Builder values.
+         */
+        @NonNull
+        public AggregateBatteryConsumer build() {
+            return new AggregateBatteryConsumer(this);
+        }
+    }
+}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 32ca92c..6b628b0 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -27,7 +27,7 @@
  *
  * @hide
  */
-public abstract class BatteryConsumer {
+public class BatteryConsumer {
 
     /**
      * Power usage component, describing the particular part of the system
@@ -72,14 +72,15 @@
     public static final int POWER_COMPONENT_WIFI = 11;
     public static final int POWER_COMPONENT_WAKELOCK = 12;
     public static final int POWER_COMPONENT_MEMORY = 13;
-    public static final int POWER_COMPONENT_PHONE = 13;
-    public static final int POWER_COMPONENT_IDLE = 15;
+    public static final int POWER_COMPONENT_PHONE = 14;
+    public static final int POWER_COMPONENT_AMBIENT_DISPLAY = 15;
+    public static final int POWER_COMPONENT_IDLE = 16;
     // Power that is re-attributed to other battery consumers. For example, for System Server
     // this represents the power attributed to apps requesting system services.
     // The value should be negative or zero.
-    public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 16;
+    public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 17;
 
-    public static final int POWER_COMPONENT_COUNT = 17;
+    public static final int POWER_COMPONENT_COUNT = 18;
 
     public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
     public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9ec6938..eec6810 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3234,6 +3234,11 @@
     public abstract int getMaxLearnedBatteryCapacity() ;
 
     /**
+     * @return The latest learned battery capacity in uAh.
+     */
+    public abstract int getLearnedBatteryCapacity();
+
+    /**
      * Return the array of discharge step durations.
      */
     public abstract LevelStepTracker getDischargeLevelStepTracker();
@@ -3925,7 +3930,9 @@
                 getStartClockTime(),
                 whichBatteryScreenOffRealtime / 1000, whichBatteryScreenOffUptime / 1000,
                 getEstimatedBatteryCapacity(),
-                getMinLearnedBatteryCapacity(), getMaxLearnedBatteryCapacity(),
+                getLearnedBatteryCapacity(),
+                getMinLearnedBatteryCapacity(),
+                getMaxLearnedBatteryCapacity(),
                 screenDozeTime / 1000);
 
 
@@ -4688,22 +4695,31 @@
             pw.println(sb.toString());
         }
 
+        final int lastLearnedBatteryCapacity = getLearnedBatteryCapacity();
+        if (lastLearnedBatteryCapacity > 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Last learned battery capacity: ");
+            sb.append(BatteryStatsHelper.makemAh(lastLearnedBatteryCapacity / 1000));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
         final int minLearnedBatteryCapacity = getMinLearnedBatteryCapacity();
         if (minLearnedBatteryCapacity > 0) {
             sb.setLength(0);
             sb.append(prefix);
-                sb.append("  Min learned battery capacity: ");
-                sb.append(BatteryStatsHelper.makemAh(minLearnedBatteryCapacity / 1000));
-                sb.append(" mAh");
+            sb.append("  Min learned battery capacity: ");
+            sb.append(BatteryStatsHelper.makemAh(minLearnedBatteryCapacity / 1000));
+            sb.append(" mAh");
             pw.println(sb.toString());
         }
         final int maxLearnedBatteryCapacity = getMaxLearnedBatteryCapacity();
         if (maxLearnedBatteryCapacity > 0) {
             sb.setLength(0);
             sb.append(prefix);
-                sb.append("  Max learned battery capacity: ");
-                sb.append(BatteryStatsHelper.makemAh(maxLearnedBatteryCapacity / 1000));
-                sb.append(" mAh");
+            sb.append("  Max learned battery capacity: ");
+            sb.append(BatteryStatsHelper.makemAh(maxLearnedBatteryCapacity / 1000));
+            sb.append(" mAh");
             pw.println(sb.toString());
         }
 
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index efdef62..6bc861f 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.util.Range;
 import android.util.SparseArray;
@@ -23,16 +24,53 @@
 import com.android.internal.os.BatteryStatsHistory;
 import com.android.internal.os.BatteryStatsHistoryIterator;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Contains a snapshot of battery attribution data, on a per-subsystem and per-UID basis.
+ * <p>
+ * The totals for the entire device are returned as AggregateBatteryConsumers, which can be
+ * obtained by calling {@link #getAggregateBatteryConsumer(int)}.
+ * <p>
+ * Power attributed to individual apps is returned as UidBatteryConsumers, see
+ * {@link #getUidBatteryConsumers()}.
  *
  * @hide
  */
 public final class BatteryUsageStats implements Parcelable {
-    private final double mConsumedPower;
+
+    /**
+     * Scope of battery stats included in a BatteryConsumer: the entire device, just
+     * the apps, etc.
+     *
+     * @hide
+     */
+    @IntDef(prefix = {"AGGREGATE_BATTERY_CONSUMER_SCOPE_"}, value = {
+            AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE,
+            AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public static @interface AggregateBatteryConsumerScope {
+    }
+
+    /**
+     * Power consumption by the entire device, since last charge.  The power usage in this
+     * scope includes both the power attributed to apps and the power unattributed to any
+     * apps.
+     */
+    public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE = 0;
+
+    /**
+     * Aggregated power consumed by all applications, combined, since last charge. This is
+     * the sum of power reported in UidBatteryConsumers.
+     */
+    public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS = 1;
+
+    public static final int AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT = 2;
+
     private final int mDischargePercentage;
     private final long mStatsStartTimestampMs;
     private final double mDischargedPowerLowerBound;
@@ -41,8 +79,8 @@
     private final long mChargeTimeRemainingMs;
     private final String[] mCustomPowerComponentNames;
     private final List<UidBatteryConsumer> mUidBatteryConsumers;
-    private final List<SystemBatteryConsumer> mSystemBatteryConsumers;
     private final List<UserBatteryConsumer> mUserBatteryConsumers;
+    private final AggregateBatteryConsumer[] mAggregateBatteryConsumers;
     private final Parcel mHistoryBuffer;
     private final List<BatteryStats.HistoryTag> mHistoryTagPool;
 
@@ -57,8 +95,7 @@
         mChargeTimeRemainingMs = builder.mChargeTimeRemainingMs;
         mCustomPowerComponentNames = builder.mCustomPowerComponentNames;
 
-        double totalPower = 0;
-
+        double totalPowerMah = 0;
         final int uidBatteryConsumerCount = builder.mUidBatteryConsumerBuilders.size();
         mUidBatteryConsumers = new ArrayList<>(uidBatteryConsumerCount);
         for (int i = 0; i < uidBatteryConsumerCount; i++) {
@@ -66,30 +103,28 @@
                     builder.mUidBatteryConsumerBuilders.valueAt(i);
             if (!uidBatteryConsumerBuilder.isExcludedFromBatteryUsageStats()) {
                 final UidBatteryConsumer consumer = uidBatteryConsumerBuilder.build();
-                totalPower += consumer.getConsumedPower();
+                totalPowerMah += consumer.getConsumedPower();
                 mUidBatteryConsumers.add(consumer);
             }
         }
 
-        final int systemBatteryConsumerCount = builder.mSystemBatteryConsumerBuilders.size();
-        mSystemBatteryConsumers = new ArrayList<>(systemBatteryConsumerCount);
-        for (int i = 0; i < systemBatteryConsumerCount; i++) {
-            final SystemBatteryConsumer consumer =
-                    builder.mSystemBatteryConsumerBuilders.valueAt(i).build();
-            totalPower += consumer.getConsumedPower() - consumer.getPowerConsumedByApps();
-            mSystemBatteryConsumers.add(consumer);
-        }
-
         final int userBatteryConsumerCount = builder.mUserBatteryConsumerBuilders.size();
         mUserBatteryConsumers = new ArrayList<>(userBatteryConsumerCount);
         for (int i = 0; i < userBatteryConsumerCount; i++) {
             final UserBatteryConsumer consumer =
                     builder.mUserBatteryConsumerBuilders.valueAt(i).build();
-            totalPower += consumer.getConsumedPower();
+            totalPowerMah += consumer.getConsumedPower();
             mUserBatteryConsumers.add(consumer);
         }
 
-        mConsumedPower = totalPower;
+        builder.getAggregateBatteryConsumerBuilder(AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(totalPowerMah);
+
+        mAggregateBatteryConsumers =
+                new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
+        for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+            mAggregateBatteryConsumers[i] = builder.mAggregateBatteryConsumersBuilders[i].build();
+        }
     }
 
     /**
@@ -101,6 +136,15 @@
     }
 
     /**
+     * Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully
+     * charged), in mAh
+     */
+    public double getConsumedPower() {
+        return mAggregateBatteryConsumers[AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE]
+                .getConsumedPower();
+    }
+
+    /**
      * Portion of battery charge drained since BatteryStats reset (e.g. due to being fully
      * charged), as percentage of the full charge in the range [0:100]
      */
@@ -136,11 +180,11 @@
     }
 
     /**
-     * Total amount of battery charge drained since BatteryStats reset (e.g. due to being fully
-     * charged), in mAh
+     * Returns a battery consumer for the specified battery consumer type.
      */
-    public double getConsumedPower() {
-        return mConsumedPower;
+    public BatteryConsumer getAggregateBatteryConsumer(
+            @AggregateBatteryConsumerScope int scope) {
+        return mAggregateBatteryConsumers[scope];
     }
 
     @NonNull
@@ -149,11 +193,6 @@
     }
 
     @NonNull
-    public List<SystemBatteryConsumer> getSystemBatteryConsumers() {
-        return mSystemBatteryConsumers;
-    }
-
-    @NonNull
     public List<UserBatteryConsumer> getUserBatteryConsumers() {
         return mUserBatteryConsumers;
     }
@@ -178,13 +217,19 @@
 
     private BatteryUsageStats(@NonNull Parcel source) {
         mStatsStartTimestampMs = source.readLong();
-        mConsumedPower = source.readDouble();
         mDischargePercentage = source.readInt();
         mDischargedPowerLowerBound = source.readDouble();
         mDischargedPowerUpperBound = source.readDouble();
         mBatteryTimeRemainingMs = source.readLong();
         mChargeTimeRemainingMs = source.readLong();
         mCustomPowerComponentNames = source.readStringArray();
+        mAggregateBatteryConsumers =
+                new AggregateBatteryConsumer[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
+        for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+            mAggregateBatteryConsumers[i] =
+                    AggregateBatteryConsumer.CREATOR.createFromParcel(source);
+            mAggregateBatteryConsumers[i].setCustomPowerComponentNames(mCustomPowerComponentNames);
+        }
         int uidCount = source.readInt();
         mUidBatteryConsumers = new ArrayList<>(uidCount);
         for (int i = 0; i < uidCount; i++) {
@@ -193,14 +238,6 @@
             consumer.setCustomPowerComponentNames(mCustomPowerComponentNames);
             mUidBatteryConsumers.add(consumer);
         }
-        int sysCount = source.readInt();
-        mSystemBatteryConsumers = new ArrayList<>(sysCount);
-        for (int i = 0; i < sysCount; i++) {
-            final SystemBatteryConsumer consumer =
-                    SystemBatteryConsumer.CREATOR.createFromParcel(source);
-            consumer.setCustomPowerComponentNames(mCustomPowerComponentNames);
-            mSystemBatteryConsumers.add(consumer);
-        }
         int userCount = source.readInt();
         mUserBatteryConsumers = new ArrayList<>(userCount);
         for (int i = 0; i < userCount; i++) {
@@ -237,21 +274,19 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeLong(mStatsStartTimestampMs);
-        dest.writeDouble(mConsumedPower);
         dest.writeInt(mDischargePercentage);
         dest.writeDouble(mDischargedPowerLowerBound);
         dest.writeDouble(mDischargedPowerUpperBound);
         dest.writeLong(mBatteryTimeRemainingMs);
         dest.writeLong(mChargeTimeRemainingMs);
         dest.writeStringArray(mCustomPowerComponentNames);
+        for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+            mAggregateBatteryConsumers[i].writeToParcel(dest, flags);
+        }
         dest.writeInt(mUidBatteryConsumers.size());
         for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
             mUidBatteryConsumers.get(i).writeToParcel(dest, flags);
         }
-        dest.writeInt(mSystemBatteryConsumers.size());
-        for (int i = mSystemBatteryConsumers.size() - 1; i >= 0; i--) {
-            mSystemBatteryConsumers.get(i).writeToParcel(dest, flags);
-        }
         dest.writeInt(mUserBatteryConsumers.size());
         for (int i = mUserBatteryConsumers.size() - 1; i >= 0; i--) {
             mUserBatteryConsumers.get(i).writeToParcel(dest, flags);
@@ -299,10 +334,10 @@
         private double mDischargedPowerUpperBoundMah;
         private long mBatteryTimeRemainingMs = -1;
         private long mChargeTimeRemainingMs = -1;
+        private final AggregateBatteryConsumer.Builder[] mAggregateBatteryConsumersBuilders =
+                new AggregateBatteryConsumer.Builder[AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT];
         private final SparseArray<UidBatteryConsumer.Builder> mUidBatteryConsumerBuilders =
                 new SparseArray<>();
-        private final SparseArray<SystemBatteryConsumer.Builder> mSystemBatteryConsumerBuilders =
-                new SparseArray<>();
         private final SparseArray<UserBatteryConsumer.Builder> mUserBatteryConsumerBuilders =
                 new SparseArray<>();
         private Parcel mHistoryBuffer;
@@ -315,6 +350,10 @@
         public Builder(@NonNull String[] customPowerComponentNames,  boolean includePowerModels) {
             mCustomPowerComponentNames = customPowerComponentNames;
             mIncludePowerModels = includePowerModels;
+            for (int i = 0; i < AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT; i++) {
+                mAggregateBatteryConsumersBuilders[i] = new AggregateBatteryConsumer.Builder(
+                        customPowerComponentNames, includePowerModels);
+            }
         }
 
         /**
@@ -386,7 +425,17 @@
         }
 
         /**
-         * Creates or returns a exiting UidBatteryConsumer, which represents battery attribution
+         * Creates or returns an AggregateBatteryConsumer builder, which represents aggregate
+         * battery consumption data for the specified scope.
+         */
+        @NonNull
+        public AggregateBatteryConsumer.Builder getAggregateBatteryConsumerBuilder(
+                @AggregateBatteryConsumerScope int scope) {
+            return mAggregateBatteryConsumersBuilders[scope];
+        }
+
+        /**
+         * Creates or returns a UidBatteryConsumer, which represents battery attribution
          * data for an individual UID.
          */
         @NonNull
@@ -403,23 +452,7 @@
         }
 
         /**
-         * Creates or returns a exiting SystemBatteryConsumer, which represents battery attribution
-         * data for a specific drain type.
-         */
-        @NonNull
-        public SystemBatteryConsumer.Builder getOrCreateSystemBatteryConsumerBuilder(
-                @SystemBatteryConsumer.DrainType int drainType) {
-            SystemBatteryConsumer.Builder builder = mSystemBatteryConsumerBuilders.get(drainType);
-            if (builder == null) {
-                builder = new SystemBatteryConsumer.Builder(mCustomPowerComponentNames,
-                        mIncludePowerModels, drainType);
-                mSystemBatteryConsumerBuilders.put(drainType, builder);
-            }
-            return builder;
-        }
-
-        /**
-         * Creates or returns a exiting UserBatteryConsumer, which represents battery attribution
+         * Creates or returns a UserBatteryConsumer, which represents battery attribution
          * data for an individual {@link UserHandle}.
          */
         @NonNull
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 7b8fdd7..5b3bc26 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -174,15 +174,13 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
      *     <li>If the calling app has carrier privileges (see {@link
      *     android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription.
      *     <li>If the calling app is the default SMS role holder (see {@link
      *     android.app.role.RoleManager#isRoleHeld(String)}).
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      * </ul>
      *
      * <p>If the calling app does not meet one of these requirements then this method will behave
diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java
deleted file mode 100644
index 7618339..0000000
--- a/core/java/android/os/SystemBatteryConsumer.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.os;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.util.Slog;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-
-/**
- * Contains power consumption data attributed to a system-wide drain type.
- *
- * {@hide}
- */
-public class SystemBatteryConsumer extends BatteryConsumer implements Parcelable {
-    private static final String TAG = "SystemBatteryConsumer";
-
-    //                           ****************
-    // This list must be kept current with atoms.proto (frameworks/base/cmds/statsd/src/atoms.proto)
-    // so the constant values must never change.
-    //                           ****************
-    @IntDef(prefix = {"DRAIN_TYPE_"}, value = {
-            DRAIN_TYPE_AMBIENT_DISPLAY,
-            // Reserved: APP
-            DRAIN_TYPE_BLUETOOTH,
-            DRAIN_TYPE_CAMERA,
-            DRAIN_TYPE_MOBILE_RADIO,
-            DRAIN_TYPE_FLASHLIGHT,
-            DRAIN_TYPE_IDLE,
-            DRAIN_TYPE_MEMORY,
-            // Reserved: OVERCOUNTED,
-            DRAIN_TYPE_PHONE,
-            DRAIN_TYPE_SCREEN,
-            // Reserved: UNACCOUNTED,
-            // Reserved: USER,
-            DRAIN_TYPE_WIFI,
-            DRAIN_TYPE_CUSTOM,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public static @interface DrainType {
-    }
-
-    public static final int DRAIN_TYPE_AMBIENT_DISPLAY = 0;
-    public static final int DRAIN_TYPE_BLUETOOTH = 2;
-    public static final int DRAIN_TYPE_CAMERA = 3;
-    public static final int DRAIN_TYPE_MOBILE_RADIO = 4;
-    public static final int DRAIN_TYPE_FLASHLIGHT = 5;
-    public static final int DRAIN_TYPE_IDLE = 6;
-    public static final int DRAIN_TYPE_MEMORY = 7;
-    public static final int DRAIN_TYPE_PHONE = 9;
-    public static final int DRAIN_TYPE_SCREEN = 10;
-    public static final int DRAIN_TYPE_WIFI = 13;
-    public static final int DRAIN_TYPE_CUSTOM = 14;
-
-    @DrainType
-    private final int mDrainType;
-
-    private final double mPowerConsumedByAppsMah;
-
-    @DrainType
-    public int getDrainType() {
-        return mDrainType;
-    }
-
-    private SystemBatteryConsumer(@NonNull SystemBatteryConsumer.Builder builder) {
-        super(builder.mPowerComponentsBuilder.build());
-        mDrainType = builder.mDrainType;
-        mPowerConsumedByAppsMah = builder.mPowerConsumedByAppsMah;
-        if (mPowerConsumedByAppsMah > getConsumedPower()) {
-            Slog.wtf(TAG,
-                    "Power attributed to apps exceeds total: drain type = " + mDrainType
-                            + " total consumed power = " + getConsumedPower()
-                            + " power consumed by apps = " + mPowerConsumedByAppsMah);
-        }
-    }
-
-    private SystemBatteryConsumer(Parcel in) {
-        super(new PowerComponents(in));
-        mDrainType = in.readInt();
-        mPowerConsumedByAppsMah = in.readDouble();
-    }
-
-    public double getPowerConsumedByApps() {
-        return mPowerConsumedByAppsMah;
-    }
-
-    /**
-     * Returns the amount of time this consumer was operating.
-     */
-    public long getUsageDurationMillis() {
-        return mPowerComponents.getMaxComponentUsageDurationMillis();
-    }
-
-    /**
-     * Writes the contents into a Parcel.
-     */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeInt(mDrainType);
-        dest.writeDouble(mPowerConsumedByAppsMah);
-    }
-
-    public static final Creator<SystemBatteryConsumer> CREATOR =
-            new Creator<SystemBatteryConsumer>() {
-                @Override
-                public SystemBatteryConsumer createFromParcel(Parcel in) {
-                    return new SystemBatteryConsumer(in);
-                }
-
-                @Override
-                public SystemBatteryConsumer[] newArray(int size) {
-                    return new SystemBatteryConsumer[size];
-                }
-            };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    /**
-     * Builder for SystemBatteryConsumer.
-     */
-    public static final class Builder extends BaseBuilder<Builder> {
-        @DrainType
-        private final int mDrainType;
-        private double mPowerConsumedByAppsMah;
-        private List<UidBatteryConsumer.Builder> mUidBatteryConsumers;
-
-        Builder(@NonNull String[] customPowerComponentNames,
-                boolean includePowerModels, @DrainType int drainType) {
-            super(customPowerComponentNames, includePowerModels);
-            mDrainType = drainType;
-        }
-
-        /**
-         * Sets the amount of power used by this system component that is attributed to apps.
-         * It should not exceed the total consumed power.
-         */
-        public Builder setPowerConsumedByApps(double powerConsumedByAppsMah) {
-            mPowerConsumedByAppsMah = powerConsumedByAppsMah;
-            return this;
-        }
-
-        /**
-         * Add a UidBatteryConsumer to this SystemBatteryConsumer. For example,
-         * the UidBatteryConsumer with the UID == {@link Process#BLUETOOTH_UID} should
-         * be added to the SystemBatteryConsumer with the drain type == {@link
-         * #DRAIN_TYPE_BLUETOOTH}.
-         * <p>
-         * Calculated power and duration components of the added battery consumers
-         * are aggregated at the time the SystemBatteryConsumer is built by the {@link #build()}
-         * method.
-         * </p>
-         */
-        public void addUidBatteryConsumer(UidBatteryConsumer.Builder uidBatteryConsumerBuilder) {
-            if (mUidBatteryConsumers == null) {
-                mUidBatteryConsumers = new ArrayList<>();
-            }
-            mUidBatteryConsumers.add(uidBatteryConsumerBuilder);
-        }
-
-        /**
-         * Creates a read-only object out of the Builder values.
-         */
-        @NonNull
-        public SystemBatteryConsumer build() {
-            if (mUidBatteryConsumers != null) {
-                for (int i = mUidBatteryConsumers.size() - 1; i >= 0; i--) {
-                    UidBatteryConsumer.Builder uidBatteryConsumer = mUidBatteryConsumers.get(i);
-                    mPowerComponentsBuilder.addPowerAndDuration(
-                            uidBatteryConsumer.mPowerComponentsBuilder);
-                }
-            }
-            return new SystemBatteryConsumer(this);
-        }
-    }
-}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 17c90d6..d490e7a 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -195,7 +195,7 @@
      * @hide Pending API
      */
     @Nullable
-    public List<PermissionInfo> queryPermissionsByGroup(@NonNull String groupName,
+    public List<PermissionInfo> queryPermissionsByGroup(@Nullable String groupName,
             @PackageManager.PermissionInfoFlags int flags) {
         try {
             final ParceledListSlice<PermissionInfo> parceledList =
diff --git a/core/java/android/service/voice/HotwordDetectionService.java b/core/java/android/service/voice/HotwordDetectionService.java
index 7e0117d..5f41174 100644
--- a/core/java/android/service/voice/HotwordDetectionService.java
+++ b/core/java/android/service/voice/HotwordDetectionService.java
@@ -52,8 +52,20 @@
 import java.util.function.IntConsumer;
 
 /**
- * Implemented by an application that wants to offer detection for hotword. The system will
- * start the service after calling {@link VoiceInteractionService#setHotwordDetectionConfig}.
+ * Implemented by an application that wants to offer detection for hotword. The service can be used
+ * for both DSP and non-DSP detectors.
+ *
+ * The system will bind an application's {@link VoiceInteractionService} first. When {@link
+ * VoiceInteractionService#createHotwordDetector(PersistableBundle, SharedMemory,
+ * HotwordDetector.Callback)} or {@link VoiceInteractionService#createAlwaysOnHotwordDetector(
+ * String, Locale, PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)} is called,
+ * the system will bind application's {@link HotwordDetectionService}. Either on a hardware
+ * trigger or on request from the {@link VoiceInteractionService}, the system calls into the
+ * {@link HotwordDetectionService} to request detection. The {@link HotwordDetectionService} then
+ * uses {@link Callback#onDetected(HotwordDetectedResult)} to inform the system that a relevant
+ * keyphrase was detected, or if applicable uses {@link Callback#onRejected(HotwordRejectedResult)}
+ * to inform the system that a keyphrase was not detected. The system then relays this result to
+ * the {@link VoiceInteractionService} through {@link HotwordDetector.Callback}.
  *
  * @hide
  */
diff --git a/core/java/android/view/DragAndDropPermissions.java b/core/java/android/view/DragAndDropPermissions.java
index 16204d8..973836a 100644
--- a/core/java/android/view/DragAndDropPermissions.java
+++ b/core/java/android/view/DragAndDropPermissions.java
@@ -19,7 +19,6 @@
 import static java.lang.Integer.toHexString;
 
 import android.app.Activity;
-import android.os.Binder;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -62,24 +61,6 @@
     private static final String TAG = "DragAndDrop";
     private static final boolean DEBUG = false;
 
-    /**
-     * Permissions for a drop can be granted in one of two ways:
-     * <ol>
-     *     <li>An app can explicitly request permissions using
-     *     {@link Activity#requestDragAndDropPermissions(DragEvent)}. In this case permissions are
-     *     revoked automatically when then activity is destroyed. See {@link #take(IBinder)}.
-     *     <li>The platform can request permissions on behalf of the app (e.g. in
-     *     {@link android.widget.Editor}). In this case permissions are revoked automatically when
-     *     the app process terminates. See {@link #takeTransient()}.
-     * </ol>
-     *
-     * <p>In order to implement the second case above, we create a static token object here. This
-     * ensures that the token stays alive for the lifetime of the app process, allowing us to
-     * revoke permissions when the app process terminates using {@link IBinder#linkToDeath} in
-     * {@code DragAndDropPermissionsHandler}.
-     */
-    private static IBinder sAppToken;
-
     private final IDragAndDropPermissions mDragAndDropPermissions;
 
     /**
@@ -128,7 +109,9 @@
     }
 
     /**
-     * Take permissions transiently. Permissions will be revoked when the app process terminates.
+     * Take permissions transiently. Permissions will be tied to this object's lifecycle; if not
+     * released explicitly, they will be released automatically when there are no more references
+     * to this object and it's garbage collected.
      *
      * <p>Note: This API is not exposed to apps.
      *
@@ -138,14 +121,10 @@
      */
     public boolean takeTransient() {
         try {
-            if (sAppToken == null) {
-                sAppToken = new Binder();
-            }
             if (DEBUG) {
-                Log.d(TAG, this + ": calling takeTransient() with process-bound token: "
-                        + toHexString(sAppToken.hashCode()));
+                Log.d(TAG, this + ": calling takeTransient()");
             }
-            mDragAndDropPermissions.takeTransient(sAppToken);
+            mDragAndDropPermissions.takeTransient();
         } catch (RemoteException e) {
             Log.w(TAG, this + ": takeTransient() failed with a RemoteException", e);
             return false;
diff --git a/core/java/android/view/OnReceiveContentListener.java b/core/java/android/view/OnReceiveContentListener.java
index 1e930e6..f90e01c 100644
--- a/core/java/android/view/OnReceiveContentListener.java
+++ b/core/java/android/view/OnReceiveContentListener.java
@@ -74,7 +74,7 @@
      * preserve the common behavior for inserting text. See the class javadoc for a sample
      * implementation.
      *
-     * <p>Handling different content
+     * <h3>Handling different content</h3>
      * <ul>
      *     <li>Text. If the {@link ContentInfo#getSource() source} is
      *     {@link ContentInfo#SOURCE_AUTOFILL autofill}, the view's content should be fully
@@ -86,6 +86,25 @@
      *     completely separate view).
      * </ul>
      *
+     * <h3>URI permissions</h3>
+     * <p>{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION Read permissions} are
+     * granted automatically by the platform for any
+     * {@link android.content.ContentResolver#SCHEME_CONTENT content URIs} in the payload passed
+     * to this listener. Permissions are transient and will be released automatically by the
+     * platform.
+     * <ul>
+     *     <li>If the {@link ContentInfo#getSource() source} is the
+     *     {@link ContentInfo#SOURCE_CLIPBOARD clipboard}, permissions are released whenever the
+     *     next copy action is performed by the user.
+     *     <li>If the source is {@link ContentInfo#SOURCE_AUTOFILL autofill}, permissions are tied
+     *     to the target {@link android.app.Activity} lifecycle (released when the activity
+     *     finishes).
+     *     <li>For other sources, permissions are tied to the passed-in {@code payload} object
+     *     (released automatically when there are no more references to it). To ensure that
+     *     permissions are not released prematurely, implementations of this listener should pass
+     *     along the {@code payload} object if processing is done on a background thread.
+     * </ul>
+     *
      * @param view The view where the content insertion was requested.
      * @param payload The content to insert and related metadata.
      *
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index ac70dff..1ae7ab0 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2468,7 +2468,7 @@
         if (Float.isNaN(sdrBrightness) || sdrBrightness > 1.0f
                 || (sdrBrightness < 0.0f && sdrBrightness != -1.0f)) {
             throw new IllegalArgumentException("sdrBrightness must be a number between 0.0f "
-                    + "and 1.0f, or -1 to turn the backlight off: " + displayBrightness);
+                    + "and 1.0f, or -1 to turn the backlight off: " + sdrBrightness);
         }
         return nativeSetDisplayBrightness(displayToken, sdrBrightness, sdrBrightnessNits,
                 displayBrightness, displayBrightnessNits);
diff --git a/core/java/android/view/inputmethod/InputContentInfo.java b/core/java/android/view/inputmethod/InputContentInfo.java
index 9aa410f..5ebd9c1 100644
--- a/core/java/android/view/inputmethod/InputContentInfo.java
+++ b/core/java/android/view/inputmethod/InputContentInfo.java
@@ -199,7 +199,12 @@
     }
 
     /**
-     * Requests a temporary read-only access permission for content URI associated with this object.
+     * Requests a temporary {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION read-only}
+     * access permission for the content URI associated with this object.
+     *
+     * <p>The lifecycle of the permission granted here is tied to this object instance. If the
+     * permission is not released explicitly via {@link #releasePermission()}, it will be
+     * released automatically when there are no more references to this object.</p>
      *
      * <p>Does nothing if the temporary permission is already granted.</p>
      */
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index eb16cef..b3f848b 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -6614,27 +6614,6 @@
     }
 
     /**
-     * Returns the {@link EdgeEffect#getType()} for the edge effects.
-     * @return the {@link EdgeEffect#getType()} for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    @EdgeEffect.EdgeEffectType
-    public int getEdgeEffectType() {
-        return mEdgeGlowTop.getType();
-    }
-
-    /**
-     * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
-     * @param type The edge effect type to use for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
-        mEdgeGlowTop.setType(type);
-        mEdgeGlowBottom.setType(type);
-        invalidate();
-    }
-
-    /**
      * Sets the recycler listener to be notified whenever a View is set aside in
      * the recycler for later reuse. This listener can be used to free resources
      * associated to the View.
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index 4d2d9e8..c11344e 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -62,9 +62,7 @@
  */
 public class EdgeEffect {
     /**
-     * This sets the default value for {@link #setType(int)} to {@link #TYPE_STRETCH} instead
-     * of {@link #TYPE_GLOW}. The type can still be overridden by the theme, view attribute,
-     * or by calling {@link #setType(int)}.
+     * This sets the edge effect to use stretch instead of glow.
      *
      * @hide
      */
@@ -73,34 +71,19 @@
     public static final long USE_STRETCH_EDGE_EFFECT_BY_DEFAULT = 171228096L;
 
     /**
-     * This sets the default value for {@link #setType(int)} to {@link #TYPE_STRETCH} instead
-     * of {@link #TYPE_GLOW} for views that instantiate with
-     * {@link #EdgeEffect(Context, AttributeSet)}, indicating use of S+ EdgeEffect support. The
-     * type can still be overridden by the theme, view attribute, or by calling
-     * {@link #setType(int)}.
-     *
-     * @hide
-     */
-    @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
-    public static final long USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED = 178807038L;
-
-    /**
      * The default blend mode used by {@link EdgeEffect}.
      */
     public static final BlendMode DEFAULT_BLEND_MODE = BlendMode.SRC_ATOP;
 
     /**
-     * Use a color edge glow for the edge effect. From XML, use
-     * <code>android:edgeEffectType="glow"</code>.
+     * Use a color edge glow for the edge effect.
      */
-    public static final int TYPE_GLOW = 0;
+    private static final int TYPE_GLOW = 0;
 
     /**
-     * Use a stretch for the edge effect. From XML, use
-     * <code>android:edgeEffectType="stretch"</code>.
+     * Use a stretch for the edge effect.
      */
-    public static final int TYPE_STRETCH = 1;
+    private static final int TYPE_STRETCH = 1;
 
     /**
      * The velocity threshold before the spring animation is considered settled.
@@ -221,7 +204,7 @@
      * @param context Context used to provide theming and resource information for the EdgeEffect
      */
     public EdgeEffect(Context context) {
-        this(context, null, Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT));
+        this(context, null);
     }
 
     /**
@@ -230,20 +213,12 @@
      * @param attrs The attributes of the XML tag that is inflating the view
      */
     public EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs) {
-        this(context, attrs,
-                Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
-                        || Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED));
-    }
-
-    private EdgeEffect(@NonNull Context context, @Nullable AttributeSet attrs,
-            boolean defaultStretch) {
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, com.android.internal.R.styleable.EdgeEffect);
         final int themeColor = a.getColor(
                 com.android.internal.R.styleable.EdgeEffect_colorEdgeEffect, 0xff666666);
-        mEdgeEffectType = a.getInt(
-                com.android.internal.R.styleable.EdgeEffect_edgeEffectType,
-                defaultStretch ? TYPE_STRETCH : TYPE_GLOW);
+        mEdgeEffectType = Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
+                ? TYPE_STRETCH : TYPE_GLOW;
         a.recycle();
 
         mPaint.setAntiAlias(true);
@@ -467,7 +442,6 @@
         if (mEdgeEffectType == TYPE_STRETCH) {
             mState = STATE_RECEDE;
             mVelocity = velocity * ON_ABSORB_VELOCITY_ADJUSTMENT;
-            mDistance = 0;
             mStartTime = AnimationUtils.currentAnimationTimeMillis();
         } else {
             mState = STATE_ABSORB;
@@ -506,17 +480,6 @@
     }
 
     /**
-     * Sets the edge effect type to use. The default without a theme attribute set is
-     * {@link EdgeEffect#TYPE_GLOW}.
-     *
-     * @param type The edge effect type to use.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    public void setType(@EdgeEffectType int type) {
-        mEdgeEffectType = type;
-    }
-
-    /**
      * Set or clear the blend mode. A blend mode defines how source pixels
      * (generated by a drawing command) are composited with the destination pixels
      * (content of the render target).
@@ -542,16 +505,6 @@
     }
 
     /**
-     * Return the edge effect type to use.
-     *
-     * @return The edge effect type to use.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    public @EdgeEffectType int getType() {
-        return mEdgeEffectType;
-    }
-
-    /**
      * Returns the blend mode. A blend mode defines how source pixels
      * (generated by a drawing command) are composited with the destination pixels
      * (content of the render target).
@@ -568,7 +521,7 @@
      * Draw into the provided canvas. Assumes that the canvas has been rotated
      * accordingly and the size has been set. The effect will be drawn the full
      * width of X=0 to X=width, beginning from Y=0 and extending to some factor <
-     * 1.f of height. The {@link #TYPE_STRETCH} effect will only be visible on a
+     * 1.f of height. The effect will only be visible on a
      * hardware canvas, e.g. {@link RenderNode#beginRecording()}.
      *
      * @param canvas Canvas to draw into
@@ -686,7 +639,7 @@
      * @return The maximum height of the edge effect
      */
     public int getMaxHeight() {
-        return (int) (mBounds.height() * MAX_GLOW_SCALE + 0.5f);
+        return (int) mHeight;
     }
 
     private void update() {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index e06d5f0..4447361 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -6684,13 +6684,13 @@
                     if (TextView.DEBUG_CURSOR) {
                         logCursor("SelectionModifierCursorController: onTouchEvent", "ACTION_UP");
                     }
+                    if (mEndHandle != null) {
+                        mEndHandle.dismissMagnifier();
+                    }
                     if (!isDragAcceleratorActive()) {
                         break;
                     }
                     updateSelection(event);
-                    if (mEndHandle != null) {
-                        mEndHandle.dismissMagnifier();
-                    }
 
                     // No longer dragging to select text, let the parent intercept events.
                     mTextView.getParent().requestDisallowInterceptTouchEvent(false);
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index e41893e..018cba7 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -308,27 +308,6 @@
     }
 
     /**
-     * Returns the {@link EdgeEffect#getType()} for the edge effects.
-     * @return the {@link EdgeEffect#getType()} for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    @EdgeEffect.EdgeEffectType
-    public int getEdgeEffectType() {
-        return mEdgeGlowLeft.getType();
-    }
-
-    /**
-     * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
-     * @param type The edge effect type to use for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
-        mEdgeGlowRight.setType(type);
-        mEdgeGlowLeft.setType(type);
-        invalidate();
-    }
-
-    /**
      * @return The maximum amount this scroll view will scroll in response to
      *   an arrow event.
      */
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 3610eb4..693b13b 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -340,27 +340,6 @@
     }
 
     /**
-     * Returns the {@link EdgeEffect#getType()} for the edge effects.
-     * @return the {@link EdgeEffect#getType()} for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    @EdgeEffect.EdgeEffectType
-    public int getEdgeEffectType() {
-        return mEdgeGlowTop.getType();
-    }
-
-    /**
-     * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
-     * @param type The edge effect type to use for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
-        mEdgeGlowTop.setType(type);
-        mEdgeGlowBottom.setType(type);
-        invalidate();
-    }
-
-    /**
      * @return The maximum amount this scroll view will scroll in response to
      *   an arrow event.
      */
@@ -368,7 +347,6 @@
         return (int) (MAX_SCROLL_FACTOR * (mBottom - mTop));
     }
 
-
     private void initScrollView() {
         mScroller = new OverScroller(getContext());
         setFocusable(true);
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index 8b2a2dc..537e797 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -22,6 +22,8 @@
 import android.annotation.NonNull;
 import android.graphics.Color;
 
+import com.android.internal.graphics.cam.Cam;
+
 /**
  * Copied from: frameworks/support/core-utils/java/android/support/v4/graphics/ColorUtils.java
  *
@@ -333,6 +335,35 @@
     }
 
     /**
+     * Convert the ARGB color to a color appearance model.
+     *
+     * The color appearance model is based on CAM16 hue and chroma, using L*a*b*'s L* as the
+     * third dimension.
+     *
+     * @param color the ARGB color to convert. The alpha component is ignored.
+     */
+    public static Cam colorToCAM(@ColorInt int color) {
+        return Cam.fromInt(color);
+    }
+
+    /**
+     * Convert a color appearance model representation to an ARGB color.
+     *
+     * Note: the returned color may have a lower chroma than requested. Whether a chroma is
+     * available depends on luminance. For example, there's no such thing as a high chroma light
+     * red, due to the limitations of our eyes and/or physics. If the requested chroma is
+     * unavailable, the highest possible chroma at the requested luminance is returned.
+     *
+     * @param hue hue, in degrees, in CAM coordinates
+     * @param chroma chroma in CAM coordinates.
+     * @param lstar perceptual luminance, L* in L*a*b*
+     */
+    @ColorInt
+    public static int CAMToColor(float hue, float chroma, float lstar) {
+        return Cam.getInt(hue, chroma, lstar);
+    }
+
+    /**
      * Set the alpha component of {@code color} to be {@code alpha}.
      */
     @ColorInt
diff --git a/core/java/com/android/internal/graphics/cam/Cam.java b/core/java/com/android/internal/graphics/cam/Cam.java
new file mode 100644
index 0000000..1ac5e50
--- /dev/null
+++ b/core/java/com/android/internal/graphics/cam/Cam.java
@@ -0,0 +1,509 @@
+/*
+ * 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.internal.graphics.cam;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.graphics.ColorUtils;
+
+/**
+ * A color appearance model, based on CAM16, extended to use L* as the lightness dimension, and
+ * coupled to a gamut mapping algorithm. Creates a color system, enables a digital design system.
+ */
+public class Cam {
+    // The maximum difference between the requested L* and the L* returned.
+    private static final float DL_MAX = 0.2f;
+    // The maximum color distance, in CAM16-UCS, between a requested color and the color returned.
+    private static final float DE_MAX = 1.0f;
+    // When the delta between the floor & ceiling of a binary search for chroma is less than this,
+    // the binary search terminates.
+    private static final float CHROMA_SEARCH_ENDPOINT = 0.4f;
+    // When the delta between the floor & ceiling of a binary search for J, lightness in CAM16,
+    // is less than this, the binary search terminates.
+    private static final float LIGHTNESS_SEARCH_ENDPOINT = 0.01f;
+
+    // CAM16 color dimensions, see getters for documentation.
+    private final float mHue;
+    private final float mChroma;
+    private final float mJ;
+    private final float mQ;
+    private final float mM;
+    private final float mS;
+
+    // Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*.
+    private final float mJstar;
+    private final float mAstar;
+    private final float mBstar;
+
+    /** Hue in CAM16 */
+    public float getHue() {
+        return mHue;
+    }
+
+    /** Chroma in CAM16 */
+    public float getChroma() {
+        return mChroma;
+    }
+
+    /** Lightness in CAM16 */
+    public float getJ() {
+        return mJ;
+    }
+
+    /**
+     * Brightness in CAM16.
+     *
+     * <p>Prefer lightness, brightness is an absolute quantity. For example, a sheet of white paper
+     * is much brighter viewed in sunlight than in indoor light, but it is the lightest object under
+     * any lighting.
+     */
+    public float getQ() {
+        return mQ;
+    }
+
+    /**
+     * Colorfulness in CAM16.
+     *
+     * <p>Prefer chroma, colorfulness is an absolute quantity. For example, a yellow toy car is much
+     * more colorful outside than inside, but it has the same chroma in both environments.
+     */
+    public float getM() {
+        return mM;
+    }
+
+    /**
+     * Saturation in CAM16.
+     *
+     * <p>Colorfulness in proportion to brightness. Prefer chroma, saturation measures colorfulness
+     * relative to the color's own brightness, where chroma is colorfulness relative to white.
+     */
+    public float getS() {
+        return mS;
+    }
+
+    /** Lightness coordinate in CAM16-UCS */
+    public float getJstar() {
+        return mJstar;
+    }
+
+    /** a* coordinate in CAM16-UCS */
+    public float getAstar() {
+        return mAstar;
+    }
+
+    /** b* coordinate in CAM16-UCS */
+    public float getBstar() {
+        return mBstar;
+    }
+
+    /** Construct a CAM16 color */
+    Cam(float hue, float chroma, float j, float q, float m, float s, float jstar, float astar,
+            float bstar) {
+        mHue = hue;
+        mChroma = chroma;
+        mJ = j;
+        mQ = q;
+        mM = m;
+        mS = s;
+        mJstar = jstar;
+        mAstar = astar;
+        mBstar = bstar;
+    }
+
+    /**
+     * Given a hue & chroma in CAM16, L* in L*a*b*, return an ARGB integer. The chroma of the color
+     * returned may, and frequently will, be lower than requested. Assumes the color is viewed in
+     * the
+     * frame defined by the sRGB standard.
+     */
+    public static int getInt(float hue, float chroma, float lstar) {
+        return getInt(hue, chroma, lstar, Frame.DEFAULT);
+    }
+
+    /**
+     * Create a color appearance model from a ARGB integer representing a color. It is assumed the
+     * color was viewed in the frame defined in the sRGB standard.
+     */
+    @NonNull
+    public static Cam fromInt(int argb) {
+        return fromIntInFrame(argb, Frame.DEFAULT);
+    }
+
+    /**
+     * Create a color appearance model from a ARGB integer representing a color, specifying the
+     * frame in which the color was viewed. Prefer Cam.fromInt.
+     */
+    @NonNull
+    public static Cam fromIntInFrame(int argb, @NonNull Frame frame) {
+        // Transform ARGB int to XYZ
+        float[] xyz = CamUtils.xyzFromInt(argb);
+
+        // Transform XYZ to 'cone'/'rgb' responses
+        float[][] matrix = CamUtils.XYZ_TO_CAM16RGB;
+        float rT = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
+        float gT = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
+        float bT = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
+
+        // Discount illuminant
+        float rD = frame.getRgbD()[0] * rT;
+        float gD = frame.getRgbD()[1] * gT;
+        float bD = frame.getRgbD()[2] * bT;
+
+        // Chromatic adaptation
+        float rAF = (float) Math.pow(frame.getFl() * Math.abs(rD) / 100.0, 0.42);
+        float gAF = (float) Math.pow(frame.getFl() * Math.abs(gD) / 100.0, 0.42);
+        float bAF = (float) Math.pow(frame.getFl() * Math.abs(bD) / 100.0, 0.42);
+        float rA = Math.signum(rD) * 400.0f * rAF / (rAF + 27.13f);
+        float gA = Math.signum(gD) * 400.0f * gAF / (gAF + 27.13f);
+        float bA = Math.signum(bD) * 400.0f * bAF / (bAF + 27.13f);
+
+        // redness-greenness
+        float a = (float) (11.0 * rA + -12.0 * gA + bA) / 11.0f;
+        // yellowness-blueness
+        float b = (float) (rA + gA - 2.0 * bA) / 9.0f;
+
+        // auxiliary components
+        float u = (20.0f * rA + 20.0f * gA + 21.0f * bA) / 20.0f;
+        float p2 = (40.0f * rA + 20.0f * gA + bA) / 20.0f;
+
+        // hue
+        float atan2 = (float) Math.atan2(b, a);
+        float atanDegrees = atan2 * 180.0f / (float) Math.PI;
+        float hue =
+                atanDegrees < 0
+                        ? atanDegrees + 360.0f
+                        : atanDegrees >= 360 ? atanDegrees - 360.0f : atanDegrees;
+        float hueRadians = hue * (float) Math.PI / 180.0f;
+
+        // achromatic response to color
+        float ac = p2 * frame.getNbb();
+
+        // CAM16 lightness and brightness
+        float j = 100.0f * (float) Math.pow(ac / frame.getAw(), frame.getC() * frame.getZ());
+        float q =
+                4.0f
+                        / frame.getC()
+                        * (float) Math.sqrt(j / 100.0f)
+                        * (frame.getAw() + 4.0f)
+                        * frame.getFlRoot();
+
+        // CAM16 chroma, colorfulness, and saturation.
+        float huePrime = (hue < 20.14) ? hue + 360 : hue;
+        float eHue = 0.25f * (float) (Math.cos(huePrime * Math.PI / 180.0 + 2.0) + 3.8);
+        float p1 = 50000.0f / 13.0f * eHue * frame.getNc() * frame.getNcb();
+        float t = p1 * (float) Math.sqrt(a * a + b * b) / (u + 0.305f);
+        float alpha =
+                (float) Math.pow(t, 0.9) * (float) Math.pow(1.64 - Math.pow(0.29, frame.getN()),
+                        0.73);
+        // CAM16 chroma, colorfulness, saturation
+        float c = alpha * (float) Math.sqrt(j / 100.0);
+        float m = c * frame.getFlRoot();
+        float s = 50.0f * (float) Math.sqrt((alpha * frame.getC()) / (frame.getAw() + 4.0f));
+
+        // CAM16-UCS components
+        float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
+        float mstar = 1.0f / 0.0228f * (float) Math.log(1.0f + 0.0228f * m);
+        float astar = mstar * (float) Math.cos(hueRadians);
+        float bstar = mstar * (float) Math.sin(hueRadians);
+
+        return new Cam(hue, c, j, q, m, s, jstar, astar, bstar);
+    }
+
+    /**
+     * Create a CAM from lightness, chroma, and hue coordinates. It is assumed those coordinates
+     * were measured in the sRGB standard frame.
+     */
+    @NonNull
+    private static Cam fromJch(float j, float c, float h) {
+        return fromJchInFrame(j, c, h, Frame.DEFAULT);
+    }
+
+    /**
+     * Create a CAM from lightness, chroma, and hue coordinates, and also specify the frame in which
+     * the color is being viewed.
+     */
+    @NonNull
+    private static Cam fromJchInFrame(float j, float c, float h, Frame frame) {
+        float q =
+                4.0f
+                        / frame.getC()
+                        * (float) Math.sqrt(j / 100.0)
+                        * (frame.getAw() + 4.0f)
+                        * frame.getFlRoot();
+        float m = c * frame.getFlRoot();
+        float alpha = c / (float) Math.sqrt(j / 100.0);
+        float s = 50.0f * (float) Math.sqrt((alpha * frame.getC()) / (frame.getAw() + 4.0f));
+
+        float hueRadians = h * (float) Math.PI / 180.0f;
+        float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
+        float mstar = 1.0f / 0.0228f * (float) Math.log(1.0 + 0.0228 * m);
+        float astar = mstar * (float) Math.cos(hueRadians);
+        float bstar = mstar * (float) Math.sin(hueRadians);
+        return new Cam(h, c, j, q, m, s, jstar, astar, bstar);
+    }
+
+    /**
+     * Distance in CAM16-UCS space between two colors.
+     *
+     * <p>Much like L*a*b* was designed to measure distance between colors, the CAM16 standard
+     * defined a color space called CAM16-UCS to measure distance between CAM16 colors.
+     */
+    public float distance(@NonNull Cam other) {
+        float dJ = getJstar() - other.getJstar();
+        float dA = getAstar() - other.getAstar();
+        float dB = getBstar() - other.getBstar();
+        double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
+        double dE = 1.41 * Math.pow(dEPrime, 0.63);
+        return (float) dE;
+    }
+
+    /** Returns perceived color as an ARGB integer, as viewed in standard sRGB frame. */
+    public int viewedInSrgb() {
+        return viewed(Frame.DEFAULT);
+    }
+
+    /** Returns color perceived in a frame as an ARGB integer. */
+    public int viewed(@NonNull Frame frame) {
+        float alpha =
+                (getChroma() == 0.0 || getJ() == 0.0)
+                        ? 0.0f
+                        : getChroma() / (float) Math.sqrt(getJ() / 100.0);
+
+        float t =
+                (float) Math.pow(alpha / Math.pow(1.64 - Math.pow(0.29, frame.getN()), 0.73),
+                        1.0 / 0.9);
+        float hRad = getHue() * (float) Math.PI / 180.0f;
+
+        float eHue = 0.25f * (float) (Math.cos(hRad + 2.0) + 3.8);
+        float ac = frame.getAw() * (float) Math.pow(getJ() / 100.0,
+                1.0 / frame.getC() / frame.getZ());
+        float p1 = eHue * (50000.0f / 13.0f) * frame.getNc() * frame.getNcb();
+        float p2 = (ac / frame.getNbb());
+
+        float hSin = (float) Math.sin(hRad);
+        float hCos = (float) Math.cos(hRad);
+
+        float gamma =
+                23.0f * (p2 + 0.305f) * t / (23.0f * p1 + 11.0f * t * hCos + 108.0f * t * hSin);
+        float a = gamma * hCos;
+        float b = gamma * hSin;
+        float rA = (460.0f * p2 + 451.0f * a + 288.0f * b) / 1403.0f;
+        float gA = (460.0f * p2 - 891.0f * a - 261.0f * b) / 1403.0f;
+        float bA = (460.0f * p2 - 220.0f * a - 6300.0f * b) / 1403.0f;
+
+        float rCBase = (float) Math.max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
+        float rC = Math.signum(rA) * (100.0f / frame.getFl()) * (float) Math.pow(rCBase,
+                1.0 / 0.42);
+        float gCBase = (float) Math.max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
+        float gC = Math.signum(gA) * (100.0f / frame.getFl()) * (float) Math.pow(gCBase,
+                1.0 / 0.42);
+        float bCBase = (float) Math.max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
+        float bC = Math.signum(bA) * (100.0f / frame.getFl()) * (float) Math.pow(bCBase,
+                1.0 / 0.42);
+        float rF = rC / frame.getRgbD()[0];
+        float gF = gC / frame.getRgbD()[1];
+        float bF = bC / frame.getRgbD()[2];
+
+
+        float[][] matrix = CamUtils.CAM16RGB_TO_XYZ;
+        float x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
+        float y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
+        float z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
+
+        int argb = ColorUtils.XYZToColor(x, y, z);
+        return argb;
+    }
+
+    /**
+     * Given a hue & chroma in CAM16, L* in L*a*b*, and the frame in which the color will be
+     * viewed,
+     * return an ARGB integer.
+     *
+     * <p>The chroma of the color returned may, and frequently will, be lower than requested. This
+     * is
+     * a fundamental property of color that cannot be worked around by engineering. For example, a
+     * red
+     * hue, with high chroma, and high L* does not exist: red hues have a maximum chroma below 10
+     * in
+     * light shades, creating pink.
+     */
+    public static int getInt(float hue, float chroma, float lstar, @NonNull Frame frame) {
+        // This is a crucial routine for building a color system, CAM16 itself is not sufficient.
+        //
+        // * Why these dimensions?
+        // Hue and chroma from CAM16 are used because they're the most accurate measures of those
+        // quantities. L* from L*a*b* is used because it correlates with luminance, luminance is
+        // used to measure contrast for a11y purposes, thus providing a key constraint on what
+        // colors
+        // can be used.
+        //
+        // * Why is this routine required to build a color system?
+        // In all perceptually accurate color spaces (i.e. L*a*b* and later), `chroma` may be
+        // impossible for a given `hue` and `lstar`.
+        // For example, a high chroma light red does not exist - chroma is limited to below 10 at
+        // light red shades, we call that pink. High chroma light green does exist, but not dark
+        // Also, when converting from another color space to RGB, the color may not be able to be
+        // represented in RGB. In those cases, the conversion process ends with RGB values
+        // outside 0-255
+        // The vast majority of color libraries surveyed simply round to 0 to 255. That is not an
+        // option for this library, as it distorts the expected luminance, and thus the expected
+        // contrast needed for a11y
+        //
+        // * What does this routine do?
+        // Dealing with colors in one color space not fitting inside RGB is, loosely referred to as
+        // gamut mapping or tone mapping. These algorithms are traditionally idiosyncratic, there is
+        // no universal answer. However, because the intent of this library is to build a system for
+        // digital design, and digital design uses luminance to measure contrast/a11y, we have one
+        // very important constraint that leads to an objective algorithm: the L* of the returned
+        // color _must_ match the requested L*.
+        //
+        // Intuitively, if the color must be distorted to fit into the RGB gamut, and the L*
+        // requested *must* be fulfilled, than the hue or chroma of the returned color will need
+        // to be different from the requested hue/chroma.
+        //
+        // After exploring both options, it was more intuitive that if the requested chroma could
+        // not be reached, it used the highest possible chroma. The alternative was finding the
+        // closest hue where the requested chroma could be reached, but that is not nearly as
+        // intuitive, as the requested hue is so fundamental to the color description.
+
+        // If the color doesn't have meaningful chroma, return a gray with the requested Lstar.
+        //
+        // Yellows are very chromatic at L = 100, and blues are very chromatic at L = 0. All the
+        // other hues are white at L = 100, and black at L = 0. To preserve consistency for users of
+        // this system, it is better to simply return white at L* > 99, and black and L* < 0.
+        if (chroma < 1.0 || Math.round(lstar) <= 0.0 || Math.round(lstar) >= 100.0) {
+            return CamUtils.intFromLstar(lstar);
+        }
+
+        hue = hue < 0 ? 0 : Math.min(360, hue);
+
+        // The highest chroma possible. Updated as binary search proceeds.
+        float high = chroma;
+
+        // The guess for the current binary search iteration. Starts off at the highest chroma,
+        // thus, if a color is possible at the requested chroma, the search can stop after one try.
+        float mid = chroma;
+        float low = 0.0f;
+        boolean isFirstLoop = true;
+
+        Cam answer = null;
+
+        while (Math.abs(low - high) >= CHROMA_SEARCH_ENDPOINT) {
+            // Given the current chroma guess, mid, and the desired hue, find J, lightness in
+            // CAM16 color space, that creates a color with L* = `lstar` in the L*a*b* color space.
+            Cam possibleAnswer = findCamByJ(hue, mid, lstar);
+
+            if (isFirstLoop) {
+                if (possibleAnswer != null) {
+                    return possibleAnswer.viewed(frame);
+                } else {
+                    // If this binary search iteration was the first iteration, and this point
+                    // has been reached, it means the requested chroma was not available at the
+                    // requested hue and L*.
+                    // Proceed to a traditional binary search that starts at the midpoint between
+                    // the requested chroma and 0.
+                    isFirstLoop = false;
+                    mid = low + (high - low) / 2.0f;
+                    continue;
+                }
+            }
+
+            if (possibleAnswer == null) {
+                // There isn't a CAM16 J that creates a color with L* `lstar`. Try a lower chroma.
+                high = mid;
+            } else {
+                answer = possibleAnswer;
+                // It is possible to create a color. Try higher chroma.
+                low = mid;
+            }
+
+            mid = low + (high - low) / 2.0f;
+        }
+
+        // There was no answer: meaning, for the desired hue, there was no chroma low enough to
+        // generate a color with the desired L*.
+        // All values of L* are possible when there is 0 chroma. Return a color with 0 chroma, i.e.
+        // a shade of gray, with the desired L*.
+        if (answer == null) {
+            return CamUtils.intFromLstar(lstar);
+        }
+
+        return answer.viewed(frame);
+    }
+
+    // Find J, lightness in CAM16 color space, that creates a color with L* = `lstar` in the L*a*b*
+    // color space.
+    //
+    // Returns null if no J could be found that generated a color with L* `lstar`.
+    @Nullable
+    private static Cam findCamByJ(float hue, float chroma, float lstar) {
+        float low = 0.0f;
+        float high = 100.0f;
+        float mid = 0.0f;
+        float bestdL = 1000.0f;
+        float bestdE = 1000.0f;
+
+        Cam bestCam = null;
+        while (Math.abs(low - high) > LIGHTNESS_SEARCH_ENDPOINT) {
+            mid = low + (high - low) / 2;
+            // Create the intended CAM color
+            Cam camBeforeClip = Cam.fromJch(mid, chroma, hue);
+            // Convert the CAM color to RGB. If the color didn't fit in RGB, during the conversion,
+            // the initial RGB values will be outside 0 to 255. The final RGB values are clipped to
+            // 0 to 255, distorting the intended color.
+            int clipped = camBeforeClip.viewedInSrgb();
+            float clippedLstar = CamUtils.lstarFromInt(clipped);
+            float dL = Math.abs(lstar - clippedLstar);
+
+            // If the clipped color's L* is within error margin...
+            if (dL < DL_MAX) {
+                // ...check if the CAM equivalent of the clipped color is far away from intended CAM
+                // color. For the intended color, use lightness and chroma from the clipped color,
+                // and the intended hue. Callers are wondering what the lightness is, they know
+                // chroma may be distorted, so the only concern here is if the hue slipped too far.
+                Cam camClipped = Cam.fromInt(clipped);
+                float dE = camClipped.distance(
+                        Cam.fromJch(camClipped.getJ(), camClipped.getChroma(), hue));
+                if (dE <= DE_MAX) {
+                    bestdL = dL;
+                    bestdE = dE;
+                    bestCam = camClipped;
+                }
+            }
+
+            // If there's no error at all, there's no need to search more.
+            //
+            // Note: this happens much more frequently than expected, but this is a very delicate
+            // property which relies on extremely precise sRGB <=> XYZ calculations, as well as fine
+            // tuning of the constants that determine error margins and when the binary search can
+            // terminate.
+            if (bestdL == 0 && bestdE == 0) {
+                break;
+            }
+
+            if (clippedLstar < lstar) {
+                low = mid;
+            } else {
+                high = mid;
+            }
+        }
+
+        return bestCam;
+    }
+
+}
diff --git a/core/java/com/android/internal/graphics/cam/CamUtils.java b/core/java/com/android/internal/graphics/cam/CamUtils.java
new file mode 100644
index 0000000..13dafdb
--- /dev/null
+++ b/core/java/com/android/internal/graphics/cam/CamUtils.java
@@ -0,0 +1,165 @@
+/*
+ * 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.internal.graphics.cam;
+
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+
+import com.android.internal.graphics.ColorUtils;
+
+/**
+ * Collection of methods for transforming between color spaces.
+ *
+ * <p>Methods are named $xFrom$Y. For example, lstarFromInt() returns L* from an ARGB integer.
+ *
+ * <p>These methods, generally, convert colors between the L*a*b*, XYZ, and sRGB spaces.
+ *
+ * <p>L*a*b* is a perceptually accurate color space. This is particularly important in the L*
+ * dimension: it measures luminance and unlike lightness measures traditionally used in UI work via
+ * RGB or HSL, this luminance transitions smoothly, permitting creation of pleasing shades of a
+ * color, and more pleasing transitions between colors.
+ *
+ * <p>XYZ is commonly used as an intermediate color space for converting between one color space to
+ * another. For example, to convert RGB to L*a*b*, first RGB is converted to XYZ, then XYZ is
+ * convered to L*a*b*.
+ *
+ * <p>sRGB is a "specification originated from work in 1990s through cooperation by Hewlett-Packard
+ * and Microsoft, and it was designed to be a standard definition of RGB for the internet, which it
+ * indeed became...The standard is based on a sampling of computer monitors at the time...The whole
+ * idea of sRGB is that if everyone assumed that RGB meant the same thing, then the results would be
+ * consistent, and reasonably good. It worked." - Fairchild, Color Models and Systems: Handbook of
+ * Color Psychology, 2015
+ */
+public final class CamUtils {
+    private CamUtils() {
+    }
+
+    // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16.
+    static final float[][] XYZ_TO_CAM16RGB = {
+            {0.401288f, 0.650173f, -0.051461f},
+            {-0.250268f, 1.204414f, 0.045854f},
+            {-0.002079f, 0.048952f, 0.953127f}
+    };
+
+    // Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates.
+    static final float[][] CAM16RGB_TO_XYZ = {
+            {1.86206786f, -1.01125463f, 0.14918677f},
+            {0.38752654f, 0.62144744f, -0.00897398f},
+            {-0.01584150f, -0.03412294f, 1.04996444f}
+    };
+
+    // Need this, XYZ coordinates in internal ColorUtils are private
+
+    // sRGB specification has D65 whitepoint - Stokes, Anderson, Chandrasekar, Motta - A Standard
+    // Default Color Space for the Internet: sRGB, 1996
+    static final float[] WHITE_POINT_D65 = {95.047f, 100.0f, 108.883f};
+
+    // This is a more precise sRGB to XYZ transformation matrix than traditionally
+    // used. It was derived using Schlomer's technique of transforming the xyY
+    // primaries to XYZ, then applying a correction to ensure mapping from sRGB
+    // 1, 1, 1 to the reference white point, D65.
+    static final float[][] SRGB_TO_XYZ = {
+            {0.41233895f, 0.35762064f, 0.18051042f},
+            {0.2126f, 0.7152f, 0.0722f},
+            {0.01932141f, 0.11916382f, 0.95034478f}
+    };
+
+    static int intFromLstar(float lstar) {
+        if (lstar < 1) {
+            return 0xff000000;
+        } else if (lstar > 99) {
+            return 0xffffffff;
+        }
+
+        // XYZ to LAB conversion routine, assume a and b are 0.
+        float fy = (lstar + 16.0f) / 116.0f;
+
+        // fz = fx = fy because a and b are 0
+        float fz = fy;
+        float fx = fy;
+
+        float kappa = 24389f / 27f;
+        float epsilon = 216f / 24389f;
+        boolean lExceedsEpsilonKappa = (lstar > 8.0f);
+        float yT = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa;
+        boolean cubeExceedEpsilon = (fy * fy * fy) > epsilon;
+        float xT = cubeExceedEpsilon ? fx * fx * fx : (116f * fx - 16f) / kappa;
+        float zT = cubeExceedEpsilon ? fz * fz * fz : (116f * fx - 16f) / kappa;
+
+        return ColorUtils.XYZToColor(xT * CamUtils.WHITE_POINT_D65[0],
+                yT * CamUtils.WHITE_POINT_D65[1], zT * CamUtils.WHITE_POINT_D65[2]);
+    }
+
+    /** Returns L* from L*a*b*, perceptual luminance, from an ARGB integer (ColorInt). */
+    public static float lstarFromInt(int argb) {
+        return lstarFromY(yFromInt(argb));
+    }
+
+    static float lstarFromY(float y) {
+        y = y / 100.0f;
+        final float e = 216.f / 24389.f;
+        float yIntermediate;
+        if (y <= e) {
+            return ((24389.f / 27.f) * y);
+        } else {
+            yIntermediate = (float) Math.cbrt(y);
+        }
+        return 116.f * yIntermediate - 16.f;
+    }
+
+    static float yFromInt(int argb) {
+        final float r = linearized(Color.red(argb));
+        final float g = linearized(Color.green(argb));
+        final float b = linearized(Color.blue(argb));
+        float[][] matrix = SRGB_TO_XYZ;
+        float y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]);
+        return y;
+    }
+
+    @NonNull
+    static float[] xyzFromInt(int argb) {
+        final float r = linearized(Color.red(argb));
+        final float g = linearized(Color.green(argb));
+        final float b = linearized(Color.blue(argb));
+
+        float[][] matrix = SRGB_TO_XYZ;
+        float x = (r * matrix[0][0]) + (g * matrix[0][1]) + (b * matrix[0][2]);
+        float y = (r * matrix[1][0]) + (g * matrix[1][1]) + (b * matrix[1][2]);
+        float z = (r * matrix[2][0]) + (g * matrix[2][1]) + (b * matrix[2][2]);
+        return new float[]{x, y, z};
+    }
+
+    static float yFromLstar(float lstar) {
+        float ke = 8.0f;
+        if (lstar > ke) {
+            return (float) Math.pow(((lstar + 16.0) / 116.0), 3) * 100f;
+        } else {
+            return lstar / (24389f / 27f) * 100f;
+        }
+    }
+
+    static float linearized(int rgbComponent) {
+        float normalized = (float) rgbComponent / 255.0f;
+
+        if (normalized <= 0.04045f) {
+            return (normalized / 12.92f) * 100.0f;
+        } else {
+            return (float) Math.pow(((normalized + 0.055f) / 1.055f), 2.4f) * 100.0f;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/graphics/cam/Frame.java b/core/java/com/android/internal/graphics/cam/Frame.java
new file mode 100644
index 0000000..c422ad1
--- /dev/null
+++ b/core/java/com/android/internal/graphics/cam/Frame.java
@@ -0,0 +1,194 @@
+/*
+ * 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.internal.graphics.cam;
+
+import android.annotation.NonNull;
+import android.util.MathUtils;
+
+/**
+ * The frame, or viewing conditions, where a color was seen. Used, along with a color, to create a
+ * color appearance model representing the color.
+ *
+ * <p>To convert a traditional color to a color appearance model, it requires knowing what
+ * conditions the color was observed in. Our perception of color depends on, for example, the tone
+ * of the light illuminating the color, how bright that light was, etc.
+ *
+ * <p>This class is modelled separately from the color appearance model itself because there are a
+ * number of calculations during the color => CAM conversion process that depend only on the viewing
+ * conditions. Caching those calculations in a Frame instance saves a significant amount of time.
+ */
+public final class Frame {
+    // Standard viewing conditions assumed in RGB specification - Stokes, Anderson, Chandrasekar,
+    // Motta - A Standard Default Color Space for the Internet: sRGB, 1996.
+    //
+    // White point = D65
+    // Luminance of adapting field: 200 / Pi / 5, units are cd/m^2.
+    //   sRGB ambient illuminance = 64 lux (per sRGB spec). However, the spec notes this is
+    //     artificially low and based on monitors in 1990s. Use 200, the sRGB spec says this is the
+    //     real average, and a survey of lux values on Wikipedia confirms this is a comfortable
+    //     default: somewhere between a very dark overcast day and office lighting.
+    //   Per CAM16 introduction paper (Li et al, 2017) Ew = pi * lw, and La = lw * Yb/Yw
+    //   Ew = ambient environment luminance, in lux.
+    //   Yb/Yw is taken to be midgray, ~20% relative luminance (XYZ Y 18.4, CIELAB L* 50).
+    //   Therefore La = (Ew / pi) * .184
+    //   La = 200 / pi * .184
+    // Image surround to 10 degrees = ~20% relative luminance = CIELAB L* 50
+    //
+    // Not from sRGB standard:
+    // Surround = average, 2.0.
+    // Discounting illuminant = false, doesn't occur for self-luminous displays
+    public static final Frame DEFAULT =
+            Frame.make(
+                    CamUtils.WHITE_POINT_D65,
+                    (float) (200.0f / Math.PI * CamUtils.yFromLstar(50.0f) / 100.f), 50.0f, 2.0f,
+                    false);
+
+    private final float mAw;
+    private final float mNbb;
+    private final float mNcb;
+    private final float mC;
+    private final float mNc;
+    private final float mN;
+    private final float[] mRgbD;
+    private final float mFl;
+    private final float mFlRoot;
+    private final float mZ;
+
+    float getAw() {
+        return mAw;
+    }
+
+    float getN() {
+        return mN;
+    }
+
+    float getNbb() {
+        return mNbb;
+    }
+
+    float getNcb() {
+        return mNcb;
+    }
+
+    float getC() {
+        return mC;
+    }
+
+    float getNc() {
+        return mNc;
+    }
+
+    @NonNull
+    float[] getRgbD() {
+        return mRgbD;
+    }
+
+    float getFl() {
+        return mFl;
+    }
+
+    float getFlRoot() {
+        return mFlRoot;
+    }
+
+    float getZ() {
+        return mZ;
+    }
+
+    private Frame(float n, float aw, float nbb, float ncb, float c, float nc, float[] rgbD,
+            float fl, float fLRoot, float z) {
+        mN = n;
+        mAw = aw;
+        mNbb = nbb;
+        mNcb = ncb;
+        mC = c;
+        mNc = nc;
+        mRgbD = rgbD;
+        mFl = fl;
+        mFlRoot = fLRoot;
+        mZ = z;
+    }
+
+    /** Create a custom frame. */
+    @NonNull
+    public static Frame make(@NonNull float[] whitepoint, float adaptingLuminance,
+            float backgroundLstar, float surround, boolean discountingIlluminant) {
+        // Transform white point XYZ to 'cone'/'rgb' responses
+        float[][] matrix = CamUtils.XYZ_TO_CAM16RGB;
+        float[] xyz = whitepoint;
+        float rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
+        float gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
+        float bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
+
+        // Scale input surround, domain (0, 2), to CAM16 surround, domain (0.8, 1.0)
+        float f = 0.8f + (surround / 10.0f);
+        // "Exponential non-linearity"
+        float c = (f >= 0.9) ? MathUtils.lerp(0.59f, 0.69f, ((f - 0.9f) * 10.0f)) : MathUtils.lerp(
+                0.525f, 0.59f, ((f - 0.8f) * 10.0f));
+        // Calculate degree of adaptation to illuminant
+        float d = discountingIlluminant ? 1.0f : f * (1.0f - ((1.0f / 3.6f) * (float) Math.exp(
+                (-adaptingLuminance - 42.0f) / 92.0f)));
+        // Per Li et al, if D is greater than 1 or less than 0, set it to 1 or 0.
+        d = (d > 1.0) ? 1.0f : (d < 0.0) ? 0.0f : d;
+        // Chromatic induction factor
+        float nc = f;
+
+        // Cone responses to the whitepoint, adjusted for illuminant discounting.
+        //
+        // Why use 100.0 instead of the white point's relative luminance?
+        //
+        // Some papers and implementations, for both CAM02 and CAM16, use the Y
+        // value of the reference white instead of 100. Fairchild's Color Appearance
+        // Models (3rd edition) notes that this is in error: it was included in the
+        // CIE 2004a report on CIECAM02, but, later parts of the conversion process
+        // account for scaling of appearance relative to the white point relative
+        // luminance. This part should simply use 100 as luminance.
+        float[] rgbD = new float[]{d * (100.0f / rW) + 1.0f - d, d * (100.0f / gW) + 1.0f - d,
+                d * (100.0f / bW) + 1.0f - d, };
+        // Luminance-level adaptation factor
+        float k = 1.0f / (5.0f * adaptingLuminance + 1.0f);
+        float k4 = k * k * k * k;
+        float k4F = 1.0f - k4;
+        float fl = (k4 * adaptingLuminance) + (0.1f * k4F * k4F * (float) Math.cbrt(
+                5.0 * adaptingLuminance));
+
+        // Intermediate factor, ratio of background relative luminance to white relative luminance
+        float n = CamUtils.yFromLstar(backgroundLstar) / whitepoint[1];
+
+        // Base exponential nonlinearity
+        // note Schlomer 2018 has a typo and uses 1.58, the correct factor is 1.48
+        float z = 1.48f + (float) Math.sqrt(n);
+
+        // Luminance-level induction factors
+        float nbb = 0.725f / (float) Math.pow(n, 0.2);
+        float ncb = nbb;
+
+        // Discounted cone responses to the white point, adjusted for post-chromatic
+        // adaptation perceptual nonlinearities.
+        float[] rgbAFactors = new float[]{(float) Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
+                (float) Math.pow(fl * rgbD[1] * gW / 100.0, 0.42), (float) Math.pow(
+                fl * rgbD[2] * bW / 100.0, 0.42)};
+
+        float[] rgbA = new float[]{(400.0f * rgbAFactors[0]) / (rgbAFactors[0] + 27.13f),
+                (400.0f * rgbAFactors[1]) / (rgbAFactors[1] + 27.13f),
+                (400.0f * rgbAFactors[2]) / (rgbAFactors[2] + 27.13f), };
+
+        float aw = ((2.0f * rgbA[0]) + rgbA[1] + (0.05f * rgbA[2])) * nbb;
+
+        return new Frame(n, aw, nbb, ncb, c, nc, rgbD, fl, (float) Math.pow(fl, 0.25), z);
+    }
+}
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 73d962e..0307268 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -20,7 +20,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UserHandle;
 import android.util.SparseArray;
 
@@ -50,10 +49,11 @@
                 BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = getMeasuredOrEstimatedPower(powerModel,
                 measuredEnergyUC, mPowerEstimator, durationMs);
-        builder.getOrCreateSystemBatteryConsumerBuilder(
-                        SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah, powerModel)
-                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, durationMs);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY,
+                        powerMah, powerModel);
     }
 
     /**
diff --git a/core/java/com/android/internal/os/AudioPowerCalculator.java b/core/java/com/android/internal/os/AudioPowerCalculator.java
index 9da8191..2eab506 100644
--- a/core/java/com/android/internal/os/AudioPowerCalculator.java
+++ b/core/java/com/android/internal/os/AudioPowerCalculator.java
@@ -17,8 +17,10 @@
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
+import android.util.SparseArray;
 
 /**
  * A {@link PowerCalculator} to calculate power consumed by audio hardware.
@@ -31,18 +33,47 @@
     // TODO(b/175344313): improve the model by taking into account different audio routes
     private final UsageBasedPowerEstimator mPowerEstimator;
 
+    private static class PowerAndDuration {
+        public long durationMs;
+        public double powerMah;
+    }
+
     public AudioPowerCalculator(PowerProfile powerProfile) {
         mPowerEstimator = new UsageBasedPowerEstimator(
                 powerProfile.getAveragePower(PowerProfile.POWER_AUDIO));
     }
 
     @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final PowerAndDuration total = new PowerAndDuration();
+
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            calculateApp(app, total, app.getBatteryStatsUid(), rawRealtimeUs);
+        }
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO, total.durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, total.powerMah);
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO, total.durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, total.powerMah);
+    }
+
+    private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
+            BatteryStats.Uid u, long rawRealtimeUs) {
         final long durationMs = mPowerEstimator.calculateDuration(u.getAudioTurnedOnTimer(),
                 rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = mPowerEstimator.calculatePower(durationMs);
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO, durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO, powerMah);
+        total.durationMs += durationMs;
+        total.powerMah += powerMah;
     }
 }
diff --git a/core/java/com/android/internal/os/BatteryChargeCalculator.java b/core/java/com/android/internal/os/BatteryChargeCalculator.java
index dc72f32..16f92ef 100644
--- a/core/java/com/android/internal/os/BatteryChargeCalculator.java
+++ b/core/java/com/android/internal/os/BatteryChargeCalculator.java
@@ -28,20 +28,28 @@
  * Estimates the battery discharge amounts.
  */
 public class BatteryChargeCalculator extends PowerCalculator {
-    private final double mBatteryCapacity;
-
-    public BatteryChargeCalculator(PowerProfile powerProfile) {
-        mBatteryCapacity = powerProfile.getBatteryCapacity();
-    }
 
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         builder.setDischargePercentage(
-                        batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED))
-                .setDischargedPowerRange(
-                        batteryStats.getLowDischargeAmountSinceCharge() * mBatteryCapacity / 100,
-                        batteryStats.getHighDischargeAmountSinceCharge() * mBatteryCapacity / 100);
+                batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED));
+
+        int batteryCapacityMah = batteryStats.getLearnedBatteryCapacity() / 1000;
+        if (batteryCapacityMah <= 0) {
+            batteryCapacityMah = batteryStats.getMinLearnedBatteryCapacity() / 1000;
+            if (batteryCapacityMah <= 0) {
+                batteryCapacityMah = batteryStats.getEstimatedBatteryCapacity();
+            }
+        }
+        final double dischargedPowerLowerBoundMah =
+                batteryStats.getLowDischargeAmountSinceCharge() * batteryCapacityMah / 100.0;
+        final double dischargedPowerUpperBoundMah =
+                batteryStats.getHighDischargeAmountSinceCharge() * batteryCapacityMah / 100.0;
+        builder.setDischargePercentage(
+                batteryStats.getDischargeAmount(BatteryStats.STATS_SINCE_CHARGED))
+                .setDischargedPowerRange(dischargedPowerLowerBoundMah,
+                        dischargedPowerUpperBoundMah);
 
         final long batteryTimeRemainingMs = batteryStats.computeBatteryTimeRemaining(rawRealtimeUs);
         if (batteryTimeRemainingMs != -1) {
@@ -52,6 +60,11 @@
         if (chargeTimeRemainingMs != -1) {
             builder.setChargeTimeRemainingMs(chargeTimeRemainingMs / 1000);
         }
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setConsumedPower(
+                        (dischargedPowerLowerBoundMah + dischargedPowerUpperBoundMah) / 2);
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 02a29085..db67bab 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -161,7 +161,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    static final int VERSION = 198;
+    static final int VERSION = 199;
 
     // The maximum number of names wakelocks we will keep track of
     // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -1056,6 +1056,7 @@
     private int mBatteryVoltageMv = -1;
     private int mEstimatedBatteryCapacityMah = -1;
 
+    private int mLastLearnedBatteryCapacityUah = -1;
     private int mMinLearnedBatteryCapacityUah = -1;
     private int mMaxLearnedBatteryCapacityUah = -1;
 
@@ -1142,6 +1143,11 @@
     }
 
     @Override
+    public int getLearnedBatteryCapacity() {
+        return mLastLearnedBatteryCapacityUah;
+    }
+
+    @Override
     public int getMinLearnedBatteryCapacity() {
         return mMinLearnedBatteryCapacityUah;
     }
@@ -11199,6 +11205,7 @@
         } else {
             mEstimatedBatteryCapacityMah = -1;
         }
+        mLastLearnedBatteryCapacityUah = -1;
         mMinLearnedBatteryCapacityUah = -1;
         mMaxLearnedBatteryCapacityUah = -1;
         mInteractiveTimer.reset(false, elapsedRealtimeUs);
@@ -13799,6 +13806,7 @@
             mRecordingHistory = DEBUG;
         }
 
+        mLastLearnedBatteryCapacityUah = chargeFullUah;
         if (mMinLearnedBatteryCapacityUah == -1) {
             mMinLearnedBatteryCapacityUah = chargeFullUah;
         } else {
@@ -15066,6 +15074,7 @@
         mDischargeCurrentLevel = in.readInt();
         mCurrentBatteryLevel = in.readInt();
         mEstimatedBatteryCapacityMah = in.readInt();
+        mLastLearnedBatteryCapacityUah = in.readInt();
         mMinLearnedBatteryCapacityUah = in.readInt();
         mMaxLearnedBatteryCapacityUah = in.readInt();
         mLowDischargeAmountSinceCharge = in.readInt();
@@ -15570,6 +15579,7 @@
         out.writeInt(mDischargeCurrentLevel);
         out.writeInt(mCurrentBatteryLevel);
         out.writeInt(mEstimatedBatteryCapacityMah);
+        out.writeInt(mLastLearnedBatteryCapacityUah);
         out.writeInt(mMinLearnedBatteryCapacityUah);
         out.writeInt(mMaxLearnedBatteryCapacityUah);
         out.writeInt(getLowDischargeAmountSinceCharge());
@@ -16071,6 +16081,7 @@
         mRealtimeStartUs = in.readLong();
         mOnBattery = in.readInt() != 0;
         mEstimatedBatteryCapacityMah = in.readInt();
+        mLastLearnedBatteryCapacityUah = in.readInt();
         mMinLearnedBatteryCapacityUah = in.readInt();
         mMaxLearnedBatteryCapacityUah = in.readInt();
         mOnBatteryInternal = false; // we are no longer really running.
@@ -16310,6 +16321,7 @@
         out.writeLong(mRealtimeStartUs);
         out.writeInt(mOnBattery ? 1 : 0);
         out.writeInt(mEstimatedBatteryCapacityMah);
+        out.writeInt(mLastLearnedBatteryCapacityUah);
         out.writeInt(mMinLearnedBatteryCapacityUah);
         out.writeInt(mMaxLearnedBatteryCapacityUah);
         mOnBatteryTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index babcea1..4989559 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -54,7 +54,7 @@
                 mPowerCalculators = new ArrayList<>();
 
                 // Power calculators are applied in the order of registration
-                mPowerCalculators.add(new BatteryChargeCalculator(mPowerProfile));
+                mPowerCalculators.add(new BatteryChargeCalculator());
                 mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile));
diff --git a/core/java/com/android/internal/os/BluetoothPowerCalculator.java b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
index 2c32e48..6e99bbb 100644
--- a/core/java/com/android/internal/os/BluetoothPowerCalculator.java
+++ b/core/java/com/android/internal/os/BluetoothPowerCalculator.java
@@ -21,7 +21,6 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.Log;
@@ -58,19 +57,11 @@
 
         final PowerAndDuration total = new PowerAndDuration();
 
-        SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
-                builder.getOrCreateSystemBatteryConsumerBuilder(
-                        SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH);
-
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
             calculateApp(app, total, query);
-            if (app.getUid() == Process.BLUETOOTH_UID) {
-                app.excludeFromBatteryUsageStats();
-                systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
-            }
         }
 
         final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
@@ -87,12 +78,18 @@
             Log.d(TAG, "Bluetooth active: time=" + (systemComponentDurationMs)
                     + " power=" + formatCharge(systemPowerMah));
         }
-        systemBatteryConsumerBuilder
-                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
-                        systemComponentDurationMs)
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, systemDurationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
-                        Math.max(systemPowerMah, total.powerMah), powerModel)
-                .setPowerConsumedByApps(total.powerMah);
+                        Math.max(systemPowerMah, total.powerMah), powerModel);
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, total.powerMah,
+                        powerModel);
     }
 
     private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
diff --git a/core/java/com/android/internal/os/CameraPowerCalculator.java b/core/java/com/android/internal/os/CameraPowerCalculator.java
index e56e7be..ddcabe8 100644
--- a/core/java/com/android/internal/os/CameraPowerCalculator.java
+++ b/core/java/com/android/internal/os/CameraPowerCalculator.java
@@ -17,6 +17,7 @@
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
 
@@ -36,6 +37,24 @@
     }
 
     @Override
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
+
+        final long durationMs = batteryStats.getCameraOnTime(rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        final double powerMah = mPowerEstimator.calculatePower(durationMs);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA, durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA, powerMah);
+    }
+
+    @Override
     protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long durationMs =
diff --git a/core/java/com/android/internal/os/CpuPowerCalculator.java b/core/java/com/android/internal/os/CpuPowerCalculator.java
index 2a55aa9..0d041c4 100644
--- a/core/java/com/android/internal/os/CpuPowerCalculator.java
+++ b/core/java/com/android/internal/os/CpuPowerCalculator.java
@@ -80,13 +80,28 @@
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        double totalPowerMah = 0;
+
         Result result = new Result();
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
             calculateApp(app, app.getBatteryStatsUid(), query, result);
+            totalPowerMah += result.powerMah;
         }
+
+        final long consumptionUC = batteryStats.getCpuMeasuredBatteryConsumptionUC();
+        final int powerModel = getPowerModel(consumptionUC, query);
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU, totalPowerMah);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU,
+                        powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY
+                                ? uCtoMah(consumptionUC) : totalPowerMah, powerModel);
     }
 
     private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index 9941e30..9b51a8e 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -15,12 +15,13 @@
  */
 package com.android.internal.os;
 
+import android.os.AggregateBatteryConsumer;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
+import android.util.SparseArray;
 
 /**
  * Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
@@ -33,33 +34,62 @@
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
+        double[] totalAppPowerMah = null;
+
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            totalAppPowerMah = calculateApp(app, app.getBatteryStatsUid(), totalAppPowerMah);
+        }
+
         final double[] customMeasuredPowerMah = calculateMeasuredEnergiesMah(
                 batteryStats.getCustomConsumerMeasuredBatteryConsumptionUC());
         if (customMeasuredPowerMah != null) {
-            final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
-                    builder.getOrCreateSystemBatteryConsumerBuilder(
-                            SystemBatteryConsumer.DRAIN_TYPE_CUSTOM);
+            final AggregateBatteryConsumer.Builder deviceBatteryConsumerBuilder =
+                    builder.getAggregateBatteryConsumerBuilder(
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
             for (int i = 0; i < customMeasuredPowerMah.length; i++) {
-                systemBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
+                deviceBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
                         customMeasuredPowerMah[i]);
             }
         }
+        if (totalAppPowerMah != null) {
+            final AggregateBatteryConsumer.Builder appsBatteryConsumerBuilder =
+                    builder.getAggregateBatteryConsumerBuilder(
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+            for (int i = 0; i < totalAppPowerMah.length; i++) {
+                appsBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
+                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
+                        totalAppPowerMah[i]);
+            }
+        }
     }
 
-    @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
-            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+    private double[] calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+            double[] totalPowerMah) {
+        double[] newTotalPowerMah = null;
         final double[] customMeasuredPowerMah = calculateMeasuredEnergiesMah(
                 u.getCustomConsumerMeasuredBatteryConsumptionUC());
         if (customMeasuredPowerMah != null) {
+            if (totalPowerMah == null) {
+                newTotalPowerMah = new double[customMeasuredPowerMah.length];
+            } else if (totalPowerMah.length != customMeasuredPowerMah.length) {
+                newTotalPowerMah = new double[customMeasuredPowerMah.length];
+                System.arraycopy(totalPowerMah, 0, newTotalPowerMah, 0,
+                        customMeasuredPowerMah.length);
+            } else {
+                newTotalPowerMah = totalPowerMah;
+            }
             for (int i = 0; i < customMeasuredPowerMah.length; i++) {
                 app.setConsumedPowerForCustomComponent(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
                         customMeasuredPowerMah[i]);
+                newTotalPowerMah[i] += customMeasuredPowerMah[i];
             }
         }
+        return newTotalPowerMah;
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/FlashlightPowerCalculator.java b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
index cbe0cde..32df17c 100644
--- a/core/java/com/android/internal/os/FlashlightPowerCalculator.java
+++ b/core/java/com/android/internal/os/FlashlightPowerCalculator.java
@@ -17,6 +17,7 @@
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
 
@@ -34,6 +35,24 @@
     }
 
     @Override
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
+            long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        super.calculate(builder, batteryStats, rawRealtimeUs, rawUptimeUs, query);
+
+        final long durationMs = batteryStats.getFlashlightOnTime(rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        final double powerMah = mPowerEstimator.calculatePower(durationMs);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, powerMah);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT, powerMah);
+    }
+
+    @Override
     protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final long durationMs = mPowerEstimator.calculateDuration(u.getFlashlightTurnedOnTimer(),
diff --git a/core/java/com/android/internal/os/GnssPowerCalculator.java b/core/java/com/android/internal/os/GnssPowerCalculator.java
index 7eb4b4a..a508e03 100644
--- a/core/java/com/android/internal/os/GnssPowerCalculator.java
+++ b/core/java/com/android/internal/os/GnssPowerCalculator.java
@@ -46,6 +46,7 @@
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        double appsPowerMah = 0;
         final double averageGnssPowerMa = getAverageGnssPower(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
@@ -55,12 +56,27 @@
             final long consumptionUC =
                     app.getBatteryStatsUid().getGnssMeasuredBatteryConsumptionUC();
             final int powerModel = getPowerModel(consumptionUC, query);
-            calculateApp(app, app.getBatteryStatsUid(), powerModel, rawRealtimeUs,
-                    averageGnssPowerMa, consumptionUC);
+            appsPowerMah += calculateApp(app, app.getBatteryStatsUid(), powerModel,
+                    rawRealtimeUs, averageGnssPowerMa, consumptionUC);
         }
+
+        final long consumptionUC = batteryStats.getGnssMeasuredBatteryConsumptionUC();
+        final int powerModel = getPowerModel(consumptionUC, query);
+        double powerMah;
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            powerMah = uCtoMah(consumptionUC);
+        } else {
+            powerMah = appsPowerMah;
+        }
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah, powerModel);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, appsPowerMah, powerModel);
     }
 
-    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+    private double calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             @BatteryConsumer.PowerModel int powerModel, long rawRealtimeUs,
             double averageGnssPowerMa, long measuredChargeUC) {
         final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
@@ -76,6 +92,7 @@
 
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_GNSS, durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS, powerMah, powerModel);
+        return powerMah;
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index 0c80deb..d33a88d 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -20,7 +20,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
@@ -53,7 +52,8 @@
         calculatePowerAndDuration(batteryStats, rawRealtimeUs, rawUptimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         if (mPowerMah != 0) {
-            builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_IDLE)
+            builder.getAggregateBatteryConsumerBuilder(
+                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE, mPowerMah)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE, mDurationMs);
         }
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index 64c68ce..e670178 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -482,7 +482,8 @@
                 // Unit is 10ms.
                 mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
                 if (mDeltaTimes[i] < 0) {
-                    Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
+                    Slog.e(mTag, "Negative delta from freq time for uid: " + uid
+                            + ", delta: " + mDeltaTimes[i]);
                     valid = false;
                 }
                 notify |= mDeltaTimes[i] > 0;
@@ -648,7 +649,8 @@
                         cb.onUidCpuTime(uid, delta);
                     }
                 } else if (delta < 0) {
-                    Slog.e(mTag, "Negative delta from active time proc: " + delta);
+                    Slog.e(mTag, "Negative delta from active time for uid: " + uid
+                            + ", delta: " + delta);
                 }
             }
         }
@@ -822,7 +824,8 @@
             for (int i = 0; i < mNumClusters; i++) {
                 mDeltaTime[i] = mCurTime[i] - lastTimes[i];
                 if (mDeltaTime[i] < 0) {
-                    Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
+                    Slog.e(mTag, "Negative delta from cluster time for uid: " + uid
+                            + ", delta: " + mDeltaTime[i]);
                     valid = false;
                 }
                 notify |= mDeltaTime[i] > 0;
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 5d5c155..09fd85e 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -4,7 +4,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UserHandle;
 import android.util.LongSparseArray;
 import android.util.SparseArray;
@@ -31,7 +30,8 @@
                 BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
-        builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_MEMORY)
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MEMORY, durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY, powerMah);
     }
diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
index 4db15a4..eb5993d 100644
--- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
+++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java
@@ -19,7 +19,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.telephony.CellSignalStrength;
@@ -105,14 +104,19 @@
         calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, consumptionUC);
 
         if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
-            builder.getOrCreateSystemBatteryConsumerBuilder(
-                        SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO)
+            builder.getAggregateBatteryConsumerBuilder(
+                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                             total.durationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                            total.remainingPowerMah + total.totalAppPowerMah,
-                            powerModel)
-                    .setPowerConsumedByApps(total.totalAppPowerMah);
+                            total.remainingPowerMah + total.totalAppPowerMah, powerModel);
+
+            builder.getAggregateBatteryConsumerBuilder(
+                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                    .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                            total.durationMs)
+                    .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                            total.totalAppPowerMah, powerModel);
         }
     }
 
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 2e3bff3..8dd463c 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -20,7 +20,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UserHandle;
 import android.util.SparseArray;
 
@@ -44,7 +43,8 @@
                 BatteryStats.STATS_SINCE_CHARGED) / 1000;
         final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
         if (phoneOnPower != 0) {
-            builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_PHONE)
+            builder.getAggregateBatteryConsumerBuilder(
+                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnTimeMs);
         }
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index dc0f719..1b3bc23 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -20,7 +20,6 @@
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
@@ -68,6 +67,7 @@
                 rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, consumptionUC);
 
         double totalAppPower = 0;
+        long totalAppDuration = 0;
 
         // Now deal with each app's UidBatteryConsumer. The results are stored in the
         // BatteryConsumer.POWER_COMPONENT_SCREEN power component, which is considered smeared,
@@ -86,6 +86,7 @@
                             .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
                                     appPowerAndDuration.powerMah, powerModel);
                     totalAppPower += appPowerAndDuration.powerMah;
+                    totalAppDuration += appPowerAndDuration.durationMs;
                 }
                 break;
             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
@@ -93,14 +94,20 @@
                 smearScreenBatteryDrain(uidBatteryConsumerBuilders, totalPowerAndDuration,
                         rawRealtimeUs);
                 totalAppPower = totalPowerAndDuration.powerMah;
+                totalAppDuration = totalPowerAndDuration.durationMs;
         }
 
-        builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
-                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
-                        totalPowerAndDuration.durationMs)
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
                         Math.max(totalPowerAndDuration.powerMah, totalAppPower), powerModel)
-                .setPowerConsumedByApps(totalAppPower);
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
+                        totalPowerAndDuration.durationMs);
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppPower, powerModel)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN, totalAppDuration);
     }
 
     /**
diff --git a/core/java/com/android/internal/os/SensorPowerCalculator.java b/core/java/com/android/internal/os/SensorPowerCalculator.java
index d18b7b1..83e5b57 100644
--- a/core/java/com/android/internal/os/SensorPowerCalculator.java
+++ b/core/java/com/android/internal/os/SensorPowerCalculator.java
@@ -19,6 +19,7 @@
 import android.hardware.SensorManager;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
 import android.util.SparseArray;
@@ -38,12 +39,32 @@
     }
 
     @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        double appsPowerMah = 0;
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            appsPowerMah += calculateApp(app, app.getBatteryStatsUid(), rawRealtimeUs);
+        }
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS, appsPowerMah);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS, appsPowerMah);
+    }
+
+    private double calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+            long rawRealtimeUs) {
+        final double powerMah = calculatePowerMah(u, rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED);
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SENSORS,
                         calculateDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED))
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS,
-                        calculatePowerMah(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED));
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS, powerMah);
+        return powerMah;
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/SystemServicePowerCalculator.java b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
index b4d5f97..a26abc2 100644
--- a/core/java/com/android/internal/os/SystemServicePowerCalculator.java
+++ b/core/java/com/android/internal/os/SystemServicePowerCalculator.java
@@ -87,6 +87,15 @@
                         systemServicePowerMah * uid.getProportionalSystemServiceUsage());
             }
         }
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+                        systemServicePowerMah);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES,
+                        systemServicePowerMah);
     }
 
     @Override
diff --git a/core/java/com/android/internal/os/VideoPowerCalculator.java b/core/java/com/android/internal/os/VideoPowerCalculator.java
index 0cad9a7..47916a6 100644
--- a/core/java/com/android/internal/os/VideoPowerCalculator.java
+++ b/core/java/com/android/internal/os/VideoPowerCalculator.java
@@ -17,8 +17,10 @@
 
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
+import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
+import android.util.SparseArray;
 
 /**
  * A {@link PowerCalculator} to calculate power consumed by video hardware.
@@ -28,18 +30,47 @@
 public class VideoPowerCalculator extends PowerCalculator {
     private final UsageBasedPowerEstimator mPowerEstimator;
 
+    private static class PowerAndDuration {
+        public long durationMs;
+        public double powerMah;
+    }
+
     public VideoPowerCalculator(PowerProfile powerProfile) {
         mPowerEstimator = new UsageBasedPowerEstimator(
                 powerProfile.getAveragePower(PowerProfile.POWER_VIDEO));
     }
 
     @Override
-    protected void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
+    public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
+        final PowerAndDuration total = new PowerAndDuration();
+
+        final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
+                builder.getUidBatteryConsumerBuilders();
+        for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
+            final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
+            calculateApp(app, total, app.getBatteryStatsUid(), rawRealtimeUs);
+        }
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO, total.durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, total.powerMah);
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO, total.durationMs)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, total.powerMah);
+    }
+
+    private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration total,
+            BatteryStats.Uid u, long rawRealtimeUs) {
         final long durationMs = mPowerEstimator.calculateDuration(u.getVideoTurnedOnTimer(),
                 rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = mPowerEstimator.calculatePower(durationMs);
         app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO, durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO, powerMah);
+        total.durationMs += durationMs;
+        total.powerMah += powerMah;
     }
 }
diff --git a/core/java/com/android/internal/os/WakelockPowerCalculator.java b/core/java/com/android/internal/os/WakelockPowerCalculator.java
index 194b6b8..d594107 100644
--- a/core/java/com/android/internal/os/WakelockPowerCalculator.java
+++ b/core/java/com/android/internal/os/WakelockPowerCalculator.java
@@ -51,6 +51,7 @@
         double osPowerMah = 0;
         long osDurationMs = 0;
         long totalAppDurationMs = 0;
+        double appPowerMah = 0;
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
@@ -60,6 +61,7 @@
             app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.durationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
             totalAppDurationMs += result.durationMs;
+            appPowerMah += result.powerMah;
 
             if (app.getUid() == Process.ROOT_UID) {
                 osBatteryConsumer = app;
@@ -71,13 +73,24 @@
         // The device has probably been awake for longer than the screen on
         // time and application wake lock time would account for.  Assign
         // this remainder to the OS, if possible.
+        calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
+                BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
         if (osBatteryConsumer != null) {
-            calculateRemaining(result, batteryStats, rawRealtimeUs, rawUptimeUs,
-                    BatteryStats.STATS_SINCE_CHARGED, osPowerMah, osDurationMs, totalAppDurationMs);
             osBatteryConsumer.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK,
                     result.durationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, result.powerMah);
         }
+
+        final long wakeTimeMillis =
+                calculateWakeTimeMillis(batteryStats, rawRealtimeUs, rawUptimeUs);
+        final double powerMah = mPowerEstimator.calculatePower(wakeTimeMillis);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK, wakeTimeMillis)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, powerMah);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK, appPowerMah);
     }
 
     @Override
@@ -145,8 +158,7 @@
     private void calculateRemaining(PowerAndDuration result, BatteryStats stats, long rawRealtimeUs,
             long rawUptimeUs, int statsType, double osPowerMah, long osDurationMs,
             long totalAppDurationMs) {
-        final long wakeTimeMillis = stats.getBatteryUptime(rawUptimeUs) / 1000
-                - stats.getScreenOnTime(rawRealtimeUs, statsType) / 1000
+        final long wakeTimeMillis = calculateWakeTimeMillis(stats, rawRealtimeUs, rawUptimeUs)
                 - totalAppDurationMs;
         if (wakeTimeMillis > 0) {
             final double power = mPowerEstimator.calculatePower(wakeTimeMillis);
@@ -157,4 +169,12 @@
             result.powerMah = osPowerMah + power;
         }
     }
+
+    private long calculateWakeTimeMillis(BatteryStats batteryStats, long rawRealtimeUs,
+            long rawUptimeUs) {
+        final long batteryUptimeUs = batteryStats.getBatteryUptime(rawUptimeUs);
+        final long screenOnTimeUs =
+                batteryStats.getScreenOnTime(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
+        return (batteryUptimeUs - screenOnTimeUs) / 1000;
+    }
 }
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index ef5b147..776a705 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -20,7 +20,6 @@
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.Log;
@@ -79,10 +78,6 @@
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
 
-        final SystemBatteryConsumer.Builder systemBatteryConsumerBuilder =
-                builder.getOrCreateSystemBatteryConsumerBuilder(
-                        SystemBatteryConsumer.DRAIN_TYPE_WIFI);
-
         long totalAppDurationMs = 0;
         double totalAppPowerMah = 0;
         final PowerDurationAndTraffic powerDurationAndTraffic = new PowerDurationAndTraffic();
@@ -104,11 +99,6 @@
                     powerDurationAndTraffic.durationMs);
             app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
                     powerDurationAndTraffic.powerMah, powerModel);
-
-            if (app.getUid() == Process.WIFI_UID) {
-                systemBatteryConsumerBuilder.addUidBatteryConsumer(app);
-                app.excludeFromBatteryUsageStats();
-            }
         }
 
         final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC();
@@ -117,12 +107,16 @@
                 BatteryStats.STATS_SINCE_CHARGED, batteryStats.hasWifiActivityReporting(),
                 totalAppDurationMs, totalAppPowerMah, consumptionUC);
 
-        systemBatteryConsumerBuilder
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI,
                         powerDurationAndTraffic.durationMs)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
-                        totalAppPowerMah + powerDurationAndTraffic.powerMah, powerModel)
-                .setPowerConsumedByApps(totalAppPowerMah);
+                        totalAppPowerMah + powerDurationAndTraffic.powerMah, powerModel);
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI,
+                        totalAppPowerMah, powerModel);
     }
 
     /**
diff --git a/core/java/com/android/internal/view/IDragAndDropPermissions.aidl b/core/java/com/android/internal/view/IDragAndDropPermissions.aidl
index edb759a..4834d22 100644
--- a/core/java/com/android/internal/view/IDragAndDropPermissions.aidl
+++ b/core/java/com/android/internal/view/IDragAndDropPermissions.aidl
@@ -24,6 +24,6 @@
  */
 interface IDragAndDropPermissions {
     void take(IBinder activityToken);
-    void takeTransient(IBinder transientToken);
+    void takeTransient();
     void release();
 }
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index 9ed5eb1..be15a9b 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -16,16 +16,10 @@
 
 package com.android.internal.widget;
 
-import static android.widget.EdgeEffect.TYPE_GLOW;
-import static android.widget.EdgeEffect.TYPE_STRETCH;
-import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_BY_DEFAULT;
-import static android.widget.EdgeEffect.USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED;
-
 import android.annotation.CallSuper;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.compat.Compatibility;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -466,8 +460,6 @@
     private final int[] mScrollConsumed = new int[2];
     private final int[] mNestedOffsets = new int[2];
 
-    private int mEdgeEffectType;
-
     /**
      * These are views that had their a11y importance changed during a layout. We defer these events
      * until the end of the layout because a11y service may make sync calls back to the RV while
@@ -595,12 +587,8 @@
             setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
         }
 
-        boolean defaultToStretch = Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_BY_DEFAULT)
-                || Compatibility.isChangeEnabled(USE_STRETCH_EDGE_EFFECT_FOR_SUPPORTED);
         TypedArray a = context.obtainStyledAttributes(attrs,
                 com.android.internal.R.styleable.EdgeEffect);
-        mEdgeEffectType = a.getInt(com.android.internal.R.styleable.EdgeEffect_edgeEffectType,
-                defaultToStretch ? TYPE_STRETCH : TYPE_GLOW);
         a.recycle();
 
         // Re-set whether nested scrolling is enabled so that it is set on all API levels
@@ -626,28 +614,6 @@
     }
 
     /**
-     * Returns the {@link EdgeEffect#getType()} used for all EdgeEffects.
-     *
-     * @return @link EdgeEffect#getType()} used for all EdgeEffects.
-     */
-    @EdgeEffect.EdgeEffectType
-    public int getEdgeEffectType() {
-        return mEdgeEffectType;
-    }
-
-    /**
-     * Sets the {@link EdgeEffect#getType()} used in all EdgeEffects.
-     * Any existing over-scroll effects are cleared and new effects are created as needed.
-     *
-     * @param type the {@link EdgeEffect#getType()} used in all EdgeEffects.
-     */
-    public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
-        mEdgeEffectType = type;
-        invalidateGlows();
-        invalidate();
-    }
-
-    /**
      * Instantiate and set a LayoutManager, if specified in the attributes.
      */
     private void createLayoutManager(Context context, String className, AttributeSet attrs,
@@ -2223,7 +2189,6 @@
             return;
         }
         mLeftGlow = new EdgeEffect(getContext());
-        mLeftGlow.setType(mEdgeEffectType);
         if (mClipToPadding) {
             mLeftGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                     getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2237,7 +2202,6 @@
             return;
         }
         mRightGlow = new EdgeEffect(getContext());
-        mRightGlow.setType(mEdgeEffectType);
         if (mClipToPadding) {
             mRightGlow.setSize(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(),
                     getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
@@ -2251,7 +2215,6 @@
             return;
         }
         mTopGlow = new EdgeEffect(getContext());
-        mTopGlow.setType(mEdgeEffectType);
         if (mClipToPadding) {
             mTopGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                     getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
@@ -2266,7 +2229,6 @@
             return;
         }
         mBottomGlow = new EdgeEffect(getContext());
-        mBottomGlow.setType(mEdgeEffectType);
         if (mClipToPadding) {
             mBottomGlow.setSize(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                     getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 93cde3d..1174db5 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -387,28 +387,6 @@
     }
 
     /**
-     * Returns the {@link EdgeEffect#getType()} for the edge effects.
-     * @return the {@link EdgeEffect#getType()} for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    @EdgeEffect.EdgeEffectType
-    public int getEdgeEffectType() {
-        // Both left and right edge have the same edge effect type
-        return mLeftEdge.getType();
-    }
-
-    /**
-     * Sets the {@link EdgeEffect#setType(int)} for the edge effects.
-     * @param type The edge effect type to use for the edge effects.
-     * @attr ref android.R.styleable#EdgeEffect_edgeEffectType
-     */
-    public void setEdgeEffectType(@EdgeEffect.EdgeEffectType int type) {
-        mLeftEdge.setType(type);
-        mRightEdge.setType(type);
-        invalidate();
-    }
-
-    /**
      * Set a PagerAdapter that will supply views for this pager as needed.
      *
      * @param adapter Adapter to use
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 480b478..f0c43ff 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1179,15 +1179,6 @@
         <!-- Color applied to effects. -->
         <attr name="effectColor" format="color" />
 
-        <!-- The type of the edge effect. The default is glow. -->
-        <attr name="edgeEffectType">
-            <!-- Use a colored glow at the edge. -->
-            <enum name="glow" value="0" />
-
-            <!-- Stretch the content. -->
-            <enum name="stretch" value="1" />
-        </attr>
-
         <!-- =================== -->
         <!-- Lighting properties -->
         <!-- =================== -->
@@ -9257,7 +9248,6 @@
     <!-- Used as a filter array on the theme to pull out only the EdgeEffect-relevant bits. -->
     <declare-styleable name="EdgeEffect">
         <attr name="colorEdgeEffect" />
-        <attr name="edgeEffectType" />
     </declare-styleable>
 
     <!-- Use <code>tv-input</code> as the root tag of the XML resource that describes a
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 0e9526a..482b112 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3064,7 +3064,6 @@
     <public name="hotwordDetectionService" />
     <public name="previewLayout" />
     <public name="clipToOutline" />
-    <public name="edgeEffectType" />
     <public name="knownCerts" />
     <public name="windowBackgroundBlurRadius"/>
     <public name="windowSplashScreenBackground"/>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_device_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_device_24.xml
new file mode 100644
index 0000000..223cdf4
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_device_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="#3ddc84">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M17,1L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-2 -2,-2zM7,6h10v10L7,16L7,6zM17,21L7,21v-3h10v3zM7,4L7,3h10v1L7,4zM10,19h4v1h-4z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index a15a8d8..24b164b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.os.BatteryConsumer;
 import android.os.BatteryUsageStats;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.UserHandle;
 import android.util.DebugUtils;
@@ -63,7 +62,7 @@
         }
 
         mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
-                context.getPackageManager(), requestedBatteryConsumer);
+                requestedBatteryConsumer, batteryConsumerId, context.getPackageManager());
 
         double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT];
         double[] totalModeledPowerByComponentMah =
@@ -119,16 +118,20 @@
 
     private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats,
             String batteryConsumerId) {
+        for (int scope = 0;
+                scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
+                scope++) {
+            if (batteryConsumerId(scope).equals(batteryConsumerId)) {
+                return batteryUsageStats.getAggregateBatteryConsumer(scope);
+            }
+        }
+
         for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
             if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
                 return consumer;
             }
         }
-        for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) {
-            if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
-                return consumer;
-            }
-        }
+
         return null;
     }
 
@@ -148,11 +151,25 @@
 
     private void computeTotalPower(BatteryUsageStats batteryUsageStats,
             double[] powerByComponentMah) {
-        for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
-            for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT;
-                    component++) {
-                powerByComponentMah[component] += consumer.getConsumedPower(component);
-            }
+        final BatteryConsumer consumer =
+                batteryUsageStats.getAggregateBatteryConsumer(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+        for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
+            powerByComponentMah[component] += consumer.getConsumedPower(component);
+        }
+    }
+
+    private void computeTotalPowerForCustomComponent(
+            BatteryUsageStats batteryUsageStats, double[] powerByComponentMah) {
+        final BatteryConsumer consumer =
+                batteryUsageStats.getAggregateBatteryConsumer(
+                        BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+        final int customComponentCount = consumer.getCustomPowerComponentCount();
+        for (int component = 0;
+                component < Math.min(customComponentCount, powerByComponentMah.length);
+                component++) {
+            powerByComponentMah[component] += consumer.getConsumedPowerForCustomComponent(
+                    BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
         }
     }
 
@@ -166,19 +183,6 @@
         }
     }
 
-    private void computeTotalPowerForCustomComponent(
-            BatteryUsageStats batteryUsageStats, double[] powerByComponentMah) {
-        for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
-            final int customComponentCount = consumer.getCustomPowerComponentCount();
-            for (int component = 0;
-                    component < Math.min(customComponentCount, powerByComponentMah.length);
-                    component++) {
-                powerByComponentMah[component] += consumer.getConsumedPowerForCustomComponent(
-                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
-            }
-        }
-    }
-
     private void addEntry(String title, EntryType entryType, double amount, double totalAmount,
             boolean isSystemBatteryConsumer) {
         Entry entry = new Entry();
@@ -203,10 +207,13 @@
             return "APP|"
                     + UserHandle.getUserId(((UidBatteryConsumer) consumer).getUid()) + "|"
                     + ((UidBatteryConsumer) consumer).getUid();
-        } else if (consumer instanceof SystemBatteryConsumer) {
-            return ((SystemBatteryConsumer) consumer).getDrainType() + "|0|0";
         } else {
             return "";
         }
     }
-}
+
+    public static String batteryConsumerId(
+            @BatteryUsageStats.AggregateBatteryConsumerScope int scope) {
+        return "SYS|" + scope;
+    }
+}
\ No newline at end of file
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
index 6288e0b..f2d6bca 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
@@ -16,11 +16,13 @@
 
 package com.android.frameworks.core.batterystatsviewer;
 
+import static com.android.frameworks.core.batterystatsviewer.BatteryConsumerData.batteryConsumerId;
+
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.os.BatteryConsumer;
+import android.os.BatteryUsageStats;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.util.DebugUtils;
 
@@ -41,10 +43,11 @@
     }
 
     @NonNull
-    public static BatteryConsumerInfo makeBatteryConsumerInfo(PackageManager packageManager,
-            @NonNull BatteryConsumer batteryConsumer) {
+    public static BatteryConsumerInfo makeBatteryConsumerInfo(
+            @NonNull BatteryConsumer batteryConsumer, String batteryConsumerId,
+            PackageManager packageManager) {
         BatteryConsumerInfo info = new BatteryConsumerInfo();
-        info.id = BatteryConsumerData.batteryConsumerId(batteryConsumer);
+        info.id = batteryConsumerId;
         info.powerMah = batteryConsumer.getConsumedPower();
 
         if (batteryConsumer instanceof UidBatteryConsumer) {
@@ -100,25 +103,29 @@
                     info.packages = sb;
                 }
             }
-        } else if (batteryConsumer instanceof SystemBatteryConsumer) {
-            final SystemBatteryConsumer systemBatteryConsumer =
-                    (SystemBatteryConsumer) batteryConsumer;
-            final int drainType = systemBatteryConsumer.getDrainType();
-            String name = DebugUtils.constantToString(SystemBatteryConsumer.class, "DRAIN_TYPE_",
-                    drainType);
-            info.label = name.charAt(0) + name.substring(1).toLowerCase().replace('_', ' ');
-            info.isSystemBatteryConsumer = true;
-        }
-
-        // Default the app icon to System Server. This includes root, dex2oat and other UIDs.
-        if (info.iconInfo == null) {
-            try {
-                info.iconInfo =
-                        packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0);
-            } catch (PackageManager.NameNotFoundException nameNotFoundException) {
-                // Won't happen
+            // Default the app icon to System Server. This includes root, dex2oat and other UIDs.
+            if (info.iconInfo == null) {
+                try {
+                    info.iconInfo =
+                            packageManager.getApplicationInfo(SYSTEM_SERVER_PACKAGE_NAME, 0);
+                } catch (PackageManager.NameNotFoundException nameNotFoundException) {
+                    // Won't happen
+                }
+            }
+        } else {
+            for (int scope = 0;
+                    scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
+                    scope++) {
+                if (batteryConsumerId(scope).equals(batteryConsumerId)) {
+                    final String name = DebugUtils.constantToString(BatteryUsageStats.class,
+                            "AGGREGATE_BATTERY_CONSUMER_SCOPE_", scope)
+                            .replace('_', ' ');
+                    info.label = name;
+                    break;
+                }
             }
         }
+
         return info;
     }
 }
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
index 63a15d6..9e63a35 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerActivity.java
@@ -18,68 +18,60 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
 import android.os.Bundle;
+import android.os.UidBatteryConsumer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
 
+import androidx.activity.ComponentActivity;
 import androidx.annotation.NonNull;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentStatePagerAdapter;
-import androidx.viewpager.widget.ViewPager;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
 
-import com.google.android.material.tabs.TabLayout;
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
 
 /**
  * Picker, showing a sorted lists of applications and other types of entities consuming power.
  * Opens BatteryStatsViewerActivity upon item selection.
  */
-public class BatteryConsumerPickerActivity extends FragmentActivity {
+public class BatteryConsumerPickerActivity extends ComponentActivity {
     private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
+    private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
+    private BatteryConsumerListAdapter mBatteryConsumerListAdapter;
+    private RecyclerView mAppList;
+    private View mLoadingView;
+    private final Runnable mBatteryStatsRefresh = this::loadBatteryStats;
+
+    private interface OnBatteryConsumerSelectedListener {
+        void onBatteryConsumerSelected(String batteryConsumerId);
+    }
 
     @Override
     protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
-        setContentView(R.layout.battery_consumer_picker_activity_layout);
+        setContentView(R.layout.battery_consumer_picker_layout);
+        mLoadingView = findViewById(R.id.loading_view);
 
-        ViewPager viewPager = findViewById(R.id.pager);
+        mAppList = findViewById(R.id.list_view);
+        mAppList.setLayoutManager(new LinearLayoutManager(this));
+        mBatteryConsumerListAdapter =
+                new BatteryConsumerListAdapter((this::setSelectedBatteryConsumer));
+        mAppList.setAdapter(mBatteryConsumerListAdapter);
 
-        FragmentStatePagerAdapter adapter = new FragmentStatePagerAdapter(
-                getSupportFragmentManager()) {
-
-            @Override
-            public int getCount() {
-                return 2;
-            }
-
-            @NonNull
-            @Override
-            public Fragment getItem(int position) {
-                switch (position) {
-                    case 0:
-                        return new BatteryConsumerPickerFragment(
-                                BatteryConsumerPickerFragment.PICKER_TYPE_APP);
-                    case 1:
-                    default:
-                        return new BatteryConsumerPickerFragment(
-                                BatteryConsumerPickerFragment.PICKER_TYPE_DRAIN);
-                }
-            }
-
-            @Override
-            public CharSequence getPageTitle(int position) {
-                switch (position) {
-                    case 0:
-                        return "Apps";
-                    case 1:
-                        return "Drains";
-                }
-                return null;
-            }
-        };
-
-        viewPager.setAdapter(adapter);
-        TabLayout tabLayout = findViewById(R.id.tab_layout);
-        tabLayout.setupWithViewPager(viewPager);
         if (icicle == null) {
             final String batteryConsumerId = getPreferences(Context.MODE_PRIVATE)
                     .getString(PREF_SELECTED_BATTERY_CONSUMER, null);
@@ -101,4 +93,183 @@
                 .putExtra(BatteryStatsViewerActivity.EXTRA_BATTERY_CONSUMER, batteryConsumerId);
         startActivity(intent);
     }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        loadBatteryStats();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        getMainThreadHandler().removeCallbacks(mBatteryStatsRefresh);
+    }
+
+    private void loadBatteryStats() {
+        LoaderManager.getInstance(this).restartLoader(0, null,
+                new BatteryConsumerListLoaderCallbacks());
+        getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS);
+    }
+
+    private static class BatteryConsumerListLoader extends
+            AsyncLoaderCompat<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> {
+        private final BatteryStatsManager mBatteryStatsManager;
+        private final PackageManager mPackageManager;
+
+        BatteryConsumerListLoader(Context context) {
+            super(context);
+            mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class);
+            mPackageManager = context.getPackageManager();
+        }
+
+        @Override
+        public List<BatteryConsumerInfoHelper.BatteryConsumerInfo> loadInBackground() {
+            final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats();
+            List<BatteryConsumerInfoHelper.BatteryConsumerInfo> batteryConsumerList =
+                    new ArrayList<>();
+
+            for (int scope = 0;
+                    scope < BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_COUNT;
+                    scope++) {
+                batteryConsumerList.add(
+                        BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
+                                batteryUsageStats.getAggregateBatteryConsumer(scope),
+                                BatteryConsumerData.batteryConsumerId(scope),
+                                mPackageManager));
+            }
+
+            for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+                batteryConsumerList.add(
+                        BatteryConsumerInfoHelper.makeBatteryConsumerInfo(consumer,
+                                BatteryConsumerData.batteryConsumerId(consumer),
+                                mPackageManager));
+            }
+
+            batteryConsumerList.sort(
+                    Comparator.comparing(
+                            (BatteryConsumerInfoHelper.BatteryConsumerInfo a) -> a.powerMah)
+                            .reversed());
+
+            return batteryConsumerList;
+        }
+
+        @Override
+        protected void onDiscardResult(List<BatteryConsumerInfoHelper.BatteryConsumerInfo> result) {
+        }
+    }
+
+    private class BatteryConsumerListLoaderCallbacks implements
+            LoaderManager.LoaderCallbacks<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> {
+
+        @NonNull
+        @Override
+        public Loader<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> onCreateLoader(int id,
+                Bundle args) {
+            return new BatteryConsumerListLoader(BatteryConsumerPickerActivity.this);
+        }
+
+        @Override
+        public void onLoadFinished(
+                @NonNull Loader<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> loader,
+                List<BatteryConsumerInfoHelper.BatteryConsumerInfo> batteryConsumerList) {
+            mBatteryConsumerListAdapter.setBatteryConsumerList(batteryConsumerList);
+            mAppList.setVisibility(View.VISIBLE);
+            mLoadingView.setVisibility(View.GONE);
+        }
+
+        @Override
+        public void onLoaderReset(
+                @NonNull Loader<List<BatteryConsumerInfoHelper.BatteryConsumerInfo>> loader) {
+        }
+    }
+
+    public class BatteryConsumerListAdapter
+            extends RecyclerView.Adapter<BatteryConsumerViewHolder> {
+        private final OnBatteryConsumerSelectedListener mListener;
+        private List<BatteryConsumerInfoHelper.BatteryConsumerInfo> mBatteryConsumerList;
+
+        public BatteryConsumerListAdapter(OnBatteryConsumerSelectedListener listener) {
+            mListener = listener;
+        }
+
+        void setBatteryConsumerList(
+                List<BatteryConsumerInfoHelper.BatteryConsumerInfo> batteryConsumerList) {
+            mBatteryConsumerList = batteryConsumerList;
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public int getItemCount() {
+            return mBatteryConsumerList.size();
+        }
+
+        @NonNull
+        @Override
+        public BatteryConsumerViewHolder onCreateViewHolder(
+                @NonNull ViewGroup viewGroup,
+                int position) {
+            LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
+            View view = layoutInflater.inflate(R.layout.battery_consumer_info_layout, viewGroup,
+                    false);
+            return new BatteryConsumerViewHolder(view, mListener);
+        }
+
+        @Override
+        public void onBindViewHolder(@NonNull BatteryConsumerViewHolder viewHolder, int position) {
+            BatteryConsumerInfoHelper.BatteryConsumerInfo item = mBatteryConsumerList.get(position);
+            viewHolder.id = item.id;
+            viewHolder.titleView.setText(item.label);
+            if (item.details != null) {
+                viewHolder.detailsView.setText(item.details);
+                viewHolder.detailsView.setVisibility(View.VISIBLE);
+            } else {
+                viewHolder.detailsView.setVisibility(View.GONE);
+            }
+            viewHolder.powerView.setText(
+                    String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
+            if (item.iconInfo != null) {
+                viewHolder.iconView.setImageDrawable(
+                        item.iconInfo.loadIcon(getPackageManager()));
+            } else {
+                viewHolder.iconView.setImageResource(R.drawable.gm_device_24);
+            }
+            if (item.packages != null) {
+                viewHolder.packagesView.setText(item.packages);
+                viewHolder.packagesView.setVisibility(View.VISIBLE);
+            } else {
+                viewHolder.packagesView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    // View Holder used when displaying apps
+    public static class BatteryConsumerViewHolder extends RecyclerView.ViewHolder
+            implements View.OnClickListener {
+        private final OnBatteryConsumerSelectedListener mListener;
+
+        public String id;
+        public TextView titleView;
+        public TextView detailsView;
+        public ImageView iconView;
+        public TextView packagesView;
+        public TextView powerView;
+
+        BatteryConsumerViewHolder(View view, OnBatteryConsumerSelectedListener listener) {
+            super(view);
+            mListener = listener;
+            view.setOnClickListener(this);
+            titleView = view.findViewById(android.R.id.title);
+            detailsView = view.findViewById(R.id.details);
+            iconView = view.findViewById(android.R.id.icon);
+            packagesView = view.findViewById(R.id.packages);
+            powerView = view.findViewById(R.id.power_mah);
+            powerView.setVisibility(View.VISIBLE);
+        }
+
+        @Override
+        public void onClick(View v) {
+            mListener.onBatteryConsumerSelected(id);
+        }
+    }
 }
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
deleted file mode 100644
index 4922087..0000000
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2008 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.frameworks.core.batterystatsviewer;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.BatteryStatsManager;
-import android.os.BatteryUsageStats;
-import android.os.Bundle;
-import android.os.SystemBatteryConsumer;
-import android.os.UidBatteryConsumer;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-import androidx.loader.app.LoaderManager;
-import androidx.loader.content.Loader;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.frameworks.core.batterystatsviewer.BatteryConsumerInfoHelper.BatteryConsumerInfo;
-import com.android.settingslib.utils.AsyncLoaderCompat;
-
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-
-/**
- * Picker, showing a sorted lists of applications or other types of entities consuming power.
- * Returns the selected entity ID or null.
- */
-public class BatteryConsumerPickerFragment extends Fragment {
-    private static final String TAG = "AppPicker";
-
-    public static final String PICKER_TYPE = "pickertype";
-
-    public static final int PICKER_TYPE_APP = 0;
-    public static final int PICKER_TYPE_DRAIN = 1;
-
-    private BatteryConsumerListAdapter mBatteryConsumerListAdapter;
-    private RecyclerView mAppList;
-    private View mLoadingView;
-
-    private interface OnBatteryConsumerSelectedListener {
-        void onBatteryConsumerSelected(String batteryConsumerId);
-    }
-
-    public BatteryConsumerPickerFragment(int pickerType) {
-        Bundle args = new Bundle();
-        args.putInt(PICKER_TYPE, pickerType);
-        setArguments(args);
-    }
-
-    public BatteryConsumerPickerFragment() {
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-        View view = inflater.inflate(R.layout.battery_consumer_picker_layout, container, false);
-        mLoadingView = view.findViewById(R.id.loading_view);
-
-        mAppList = view.findViewById(R.id.list_view);
-        mAppList.setLayoutManager(new LinearLayoutManager(getContext()));
-        mBatteryConsumerListAdapter = new BatteryConsumerListAdapter(
-                BatteryConsumerPickerFragment.this::setSelectedBatteryConsumer);
-        mAppList.setAdapter(mBatteryConsumerListAdapter);
-
-        LoaderManager.getInstance(this).initLoader(0, getArguments(),
-                new BatteryConsumerListLoaderCallbacks());
-        return view;
-    }
-
-    public void setSelectedBatteryConsumer(String id) {
-        ((BatteryConsumerPickerActivity) getActivity()).setSelectedBatteryConsumer(id);
-    }
-
-    private static class BatteryConsumerListLoader extends
-            AsyncLoaderCompat<List<BatteryConsumerInfo>> {
-        private final int mPickerType;
-        private final BatteryStatsManager mBatteryStatsManager;
-        private final PackageManager mPackageManager;
-
-        BatteryConsumerListLoader(Context context, int pickerType) {
-            super(context);
-            mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class);
-            mPickerType = pickerType;
-            mPackageManager = context.getPackageManager();
-        }
-
-        @Override
-        public List<BatteryConsumerInfo> loadInBackground() {
-            final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats();
-
-            List<BatteryConsumerInfo> batteryConsumerList = new ArrayList<>();
-            switch (mPickerType) {
-                case PICKER_TYPE_APP:
-                    for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
-                        batteryConsumerList.add(
-                                BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
-                                        consumer));
-                    }
-                    break;
-                case PICKER_TYPE_DRAIN:
-                default:
-                    for (SystemBatteryConsumer consumer :
-                            batteryUsageStats.getSystemBatteryConsumers()) {
-                        batteryConsumerList.add(
-                                BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
-                                        consumer));
-                    }
-                    break;
-            }
-
-            batteryConsumerList.sort(
-                    Comparator.comparing((BatteryConsumerInfo a) -> a.powerMah).reversed());
-            return batteryConsumerList;
-        }
-
-        @Override
-        protected void onDiscardResult(List<BatteryConsumerInfo> result) {
-        }
-    }
-
-    private class BatteryConsumerListLoaderCallbacks implements
-            LoaderManager.LoaderCallbacks<List<BatteryConsumerInfo>> {
-
-        @NonNull
-        @Override
-        public Loader<List<BatteryConsumerInfo>> onCreateLoader(int id, Bundle args) {
-            return new BatteryConsumerListLoader(getContext(), args.getInt(PICKER_TYPE));
-        }
-
-        @Override
-        public void onLoadFinished(@NonNull Loader<List<BatteryConsumerInfo>> loader,
-                List<BatteryConsumerInfo> batteryConsumerList) {
-            mBatteryConsumerListAdapter.setBatteryConsumerList(batteryConsumerList);
-            mAppList.setVisibility(View.VISIBLE);
-            mLoadingView.setVisibility(View.GONE);
-        }
-
-        @Override
-        public void onLoaderReset(
-                @NonNull Loader<List<BatteryConsumerInfo>> loader) {
-        }
-    }
-
-    public class BatteryConsumerListAdapter extends
-            RecyclerView.Adapter<BatteryConsumerViewHolder> {
-        private final OnBatteryConsumerSelectedListener mListener;
-        private List<BatteryConsumerInfo> mBatteryConsumerList;
-
-        public BatteryConsumerListAdapter(OnBatteryConsumerSelectedListener listener) {
-            mListener = listener;
-        }
-
-        void setBatteryConsumerList(List<BatteryConsumerInfo> batteryConsumerList) {
-            mBatteryConsumerList = batteryConsumerList;
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public int getItemCount() {
-            return mBatteryConsumerList.size();
-        }
-
-        @NonNull
-        @Override
-        public BatteryConsumerViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
-                int position) {
-            LayoutInflater layoutInflater = LayoutInflater.from(viewGroup.getContext());
-            View view = layoutInflater.inflate(R.layout.battery_consumer_info_layout, viewGroup,
-                    false);
-            return new BatteryConsumerViewHolder(view, mListener);
-        }
-
-        @Override
-        public void onBindViewHolder(@NonNull BatteryConsumerViewHolder viewHolder, int position) {
-            BatteryConsumerInfo item = mBatteryConsumerList.get(position);
-            viewHolder.id = item.id;
-            viewHolder.titleView.setText(item.label);
-            if (item.details != null) {
-                viewHolder.detailsView.setText(item.details);
-                viewHolder.detailsView.setVisibility(View.VISIBLE);
-            } else {
-                viewHolder.detailsView.setVisibility(View.GONE);
-            }
-            viewHolder.powerView.setText(
-                    String.format(Locale.getDefault(), "%.1f mAh", item.powerMah));
-            viewHolder.iconView.setImageDrawable(
-                    item.iconInfo.loadIcon(getContext().getPackageManager()));
-            if (item.packages != null) {
-                viewHolder.packagesView.setText(item.packages);
-                viewHolder.packagesView.setVisibility(View.VISIBLE);
-            } else {
-                viewHolder.packagesView.setVisibility(View.GONE);
-            }
-        }
-    }
-
-    // View Holder used when displaying apps
-    public static class BatteryConsumerViewHolder extends RecyclerView.ViewHolder
-            implements View.OnClickListener {
-        private final OnBatteryConsumerSelectedListener mListener;
-
-        public String id;
-        public TextView titleView;
-        public TextView detailsView;
-        public ImageView iconView;
-        public TextView packagesView;
-        public TextView powerView;
-
-        BatteryConsumerViewHolder(View view, OnBatteryConsumerSelectedListener listener) {
-            super(view);
-            mListener = listener;
-            view.setOnClickListener(this);
-            titleView = view.findViewById(android.R.id.title);
-            detailsView = view.findViewById(R.id.details);
-            iconView = view.findViewById(android.R.id.icon);
-            packagesView = view.findViewById(R.id.packages);
-            powerView = view.findViewById(R.id.power_mah);
-            powerView.setVisibility(View.VISIBLE);
-        }
-
-        @Override
-        public void onClick(View v) {
-            mListener.onBatteryConsumerSelected(id);
-        }
-    }
-}
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index 03dde04..bb75be4 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -51,7 +51,7 @@
     private static final int LOADER_BATTERY_USAGE_STATS = 1;
 
     private BatteryStatsDataAdapter mBatteryStatsDataAdapter;
-    private final Runnable mBatteryStatsRefresh = this::periodicBatteryStatsRefresh;
+    private final Runnable mBatteryStatsRefresh = this::loadBatteryStats;
     private String mBatteryConsumerId;
     private TextView mTitleView;
     private TextView mDetailsView;
@@ -85,13 +85,15 @@
         mLoadingView = findViewById(R.id.loading_view);
         mEmptyView = findViewById(R.id.empty_view);
 
-        loadBatteryStats();
+        LoaderManager loaderManager = LoaderManager.getInstance(this);
+        loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null,
+                new BatteryUsageStatsLoaderCallbacks());
     }
 
     @Override
     protected void onResume() {
         super.onResume();
-        periodicBatteryStatsRefresh();
+        loadBatteryStats();
     }
 
     @Override
@@ -100,15 +102,11 @@
         getMainThreadHandler().removeCallbacks(mBatteryStatsRefresh);
     }
 
-    private void periodicBatteryStatsRefresh() {
-        loadBatteryStats();
-        getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS);
-    }
-
     private void loadBatteryStats() {
         LoaderManager loaderManager = LoaderManager.getInstance(this);
         loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null,
                 new BatteryUsageStatsLoaderCallbacks());
+        getMainThreadHandler().postDelayed(mBatteryStatsRefresh, BATTERY_STATS_REFRESH_RATE_MILLIS);
     }
 
     private static class BatteryUsageStatsLoader extends
@@ -183,9 +181,12 @@
             } else {
                 mDetailsView.setVisibility(View.GONE);
             }
-            mIconView.setImageDrawable(
-                    batteryConsumerInfo.iconInfo.loadIcon(getPackageManager()));
-
+            if (batteryConsumerInfo.iconInfo != null) {
+                mIconView.setImageDrawable(
+                        batteryConsumerInfo.iconInfo.loadIcon(getPackageManager()));
+            } else {
+                mIconView.setImageResource(R.drawable.gm_device_24);
+            }
             if (batteryConsumerInfo.packages != null) {
                 mPackagesView.setText(batteryConsumerInfo.packages);
                 mPackagesView.setVisibility(View.VISIBLE);
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 2e2e6bd..6f17ea9 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -185,15 +185,6 @@
     }
 
     @Test
-    public void testHandleActivity_assetsChanged() {
-        relaunchActivityAndAssertPreserveWindow(activity -> {
-            // Relaunches all activities.
-            activity.getActivityThread().handleApplicationInfoChanged(
-                    activity.getApplicationInfo());
-        });
-    }
-
-    @Test
     public void testRecreateActivity() {
         relaunchActivityAndAssertPreserveWindow(Activity::recreate);
     }
diff --git a/core/tests/coretests/src/android/colormodel/CamTest.java b/core/tests/coretests/src/android/colormodel/CamTest.java
new file mode 100644
index 0000000..a70ecd72
--- /dev/null
+++ b/core/tests/coretests/src/android/colormodel/CamTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.internal.graphics.cam;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CamTest {
+    static final int BLACK = 0xff000000;
+    static final int WHITE = 0xffffffff;
+    static final int MIDGRAY = 0xff777777;
+
+    static final int RED = 0xffff0000;
+    static final int GREEN = 0xff00ff00;
+    static final int BLUE = 0xff0000ff;
+
+    @Test
+    public void camFromIntToInt() {
+        Cam cam = Cam.fromInt(RED);
+        int color = cam.viewed(Frame.DEFAULT);
+        assertEquals(color, RED);
+    }
+
+    @Test
+    public void yFromMidgray() {
+        assertEquals(18.418f, CamUtils.yFromLstar(50.0f), 0.001);
+    }
+
+    @Test
+    public void yFromBlack() {
+        assertEquals(0.0f, CamUtils.yFromLstar(0.0f), 0.001);
+    }
+
+    @Test
+    public void yFromWhite() {
+        assertEquals(100.0f, CamUtils.yFromLstar(100.0f), 0.001);
+    }
+
+    @Test
+    public void camFromRed() {
+        Cam cam = Cam.fromInt(RED);
+        assertEquals(46.445f, cam.getJ(), 0.001f);
+        assertEquals(113.357f, cam.getChroma(), 0.001f);
+        assertEquals(27.408f, cam.getHue(), 0.001f);
+        assertEquals(89.494f, cam.getM(), 0.001f);
+        assertEquals(91.889f, cam.getS(), 0.001f);
+        assertEquals(105.988f, cam.getQ(), 0.001f);
+    }
+
+    @Test
+    public void camFromGreen() {
+        Cam cam = Cam.fromInt(GREEN);
+        assertEquals(79.331f, cam.getJ(), 0.001f);
+        assertEquals(108.409f, cam.getChroma(), 0.001f);
+        assertEquals(142.139f, cam.getHue(), 0.001f);
+        assertEquals(85.587f, cam.getM(), 0.001f);
+        assertEquals(78.604f, cam.getS(), 0.001f);
+        assertEquals(138.520, cam.getQ(), 0.001f);
+    }
+
+    @Test
+    public void camFromBlue() {
+        Cam cam = Cam.fromInt(BLUE);
+        assertEquals(25.465f, cam.getJ(), 0.001f);
+        assertEquals(87.230f, cam.getChroma(), 0.001f);
+        assertEquals(282.788f, cam.getHue(), 0.001f);
+        assertEquals(68.867f, cam.getM(), 0.001f);
+        assertEquals(93.674f, cam.getS(), 0.001f);
+        assertEquals(78.481f, cam.getQ(), 0.001f);
+    }
+
+    @Test
+    public void camFromBlack() {
+        Cam cam = Cam.fromInt(BLACK);
+        assertEquals(0.0f, cam.getJ(), 0.001f);
+        assertEquals(0.0f, cam.getChroma(), 0.001f);
+        assertEquals(0.0f, cam.getHue(), 0.001f);
+        assertEquals(0.0f, cam.getM(), 0.001f);
+        assertEquals(0.0f, cam.getS(), 0.001f);
+        assertEquals(0.0f, cam.getQ(), 0.001f);
+    }
+
+    @Test
+    public void camFromWhite() {
+        Cam cam = Cam.fromInt(WHITE);
+        assertEquals(100.0f, cam.getJ(), 0.001f);
+        assertEquals(2.869f, cam.getChroma(), 0.001f);
+        assertEquals(209.492f, cam.getHue(), 0.001f);
+        assertEquals(2.265f, cam.getM(), 0.001f);
+        assertEquals(12.068f, cam.getS(), 0.001f);
+        assertEquals(155.521, cam.getQ(), 0.001f);
+    }
+
+    @Test
+    public void getRedFromGamutMap() {
+        int colorToTest = RED;
+        Cam cam = Cam.fromInt(colorToTest);
+        int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+        assertEquals(colorToTest, color);
+    }
+
+    @Test
+    public void getGreenFromGamutMap() {
+        int colorToTest = GREEN;
+        Cam cam = Cam.fromInt(colorToTest);
+        int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+        assertEquals(colorToTest, color);
+    }
+
+    @Test
+    public void getBlueFromGamutMap() {
+        int colorToTest = BLUE;
+        Cam cam = Cam.fromInt(colorToTest);
+        int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+        assertEquals(colorToTest, color);
+    }
+
+    @Test
+    public void getWhiteFromGamutMap() {
+        int colorToTest = WHITE;
+        Cam cam = Cam.fromInt(colorToTest);
+        int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+        assertEquals(colorToTest, color);
+    }
+
+    @Test
+    public void getBlackFromGamutMap() {
+        int colorToTest = BLACK;
+        Cam cam = Cam.fromInt(colorToTest);
+        int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+        assertEquals(colorToTest, color);
+    }
+
+    @Test
+    public void getMidgrayFromGamutMap() {
+        int colorToTest = MIDGRAY;
+        Cam cam = Cam.fromInt(colorToTest);
+        int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+        assertEquals(colorToTest, color);
+    }
+
+    @Test
+    public void getRandomGreenFromGamutMap() {
+        int colorToTest = 0xff009200;
+        Cam cam = Cam.fromInt(colorToTest);
+        int color = Cam.getInt(cam.getHue(), cam.getChroma(), CamUtils.lstarFromInt(colorToTest));
+        assertEquals(colorToTest, color);
+    }
+
+    @Test
+    public void gamutMapArbitraryHCL() {
+        int color = Cam.getInt(309.0f, 40.0f, 70.0f);
+        Cam cam = Cam.fromInt(color);
+
+        assertEquals(308.759f, cam.getHue(), 0.001);
+        assertEquals(40.148f, cam.getChroma(), 0.001);
+        assertEquals(70.029f, CamUtils.lstarFromInt(color), 0.001f);
+    }
+
+    @Test
+    public void ucsCoordinates() {
+        Cam cam = Cam.fromInt(RED);
+
+        assertEquals(59.584f, cam.getJstar(), 0.001f);
+        assertEquals(43.297f, cam.getAstar(), 0.001f);
+        assertEquals(22.451f, cam.getBstar(), 0.001f);
+    }
+
+    @Test
+    public void deltaEWhiteToBlack() {
+        assertEquals(25.661f, Cam.fromInt(WHITE).distance(Cam.fromInt(BLACK)), 0.001f);
+    }
+
+    @Test
+    public void deltaERedToBlue() {
+        assertEquals(21.415f, Cam.fromInt(RED).distance(Cam.fromInt(BLUE)), 0.001f);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index 236c3da..4a5528d 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.BatteryConsumer;
-import android.os.SystemBatteryConsumer;
 import android.view.Display;
 
 import androidx.test.filters.SmallTest;
@@ -62,15 +61,13 @@
 
         mStatsRule.apply(calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(
-                        SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+        BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isEqualTo(90 * MINUTE_IN_MS);
         // 100,000,00 uC / 1000 (micro-/milli-) / 360 (seconds/hour) = 27.777778 mAh
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(27.777778);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
@@ -88,14 +85,12 @@
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(
-                        SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+        BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isEqualTo(90 * MINUTE_IN_MS);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(15.0);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
index c694d67..81940ed 100644
--- a/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AudioPowerCalculatorTest.java
@@ -56,5 +56,19 @@
                 .isEqualTo(1000);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
                 .isWithin(PRECISION).of(0.1);
+
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO))
+                .isEqualTo(1000);
+        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+                .isWithin(PRECISION).of(0.1);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appsBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AUDIO))
+                .isEqualTo(1000);
+        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
+                .isWithin(PRECISION).of(0.1);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
index cf126c6..23fc35d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryChargeCalculatorTest.java
@@ -36,18 +36,12 @@
 
     @Rule
     public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 4000.0);
+            .setAveragePower(PowerProfile.POWER_BATTERY_CAPACITY, 1234.0); // Should be ignored
 
     @Test
     public void testDischargeTotals() {
-        BatteryChargeCalculator calculator =
-                new BatteryChargeCalculator(mStatsRule.getPowerProfile());
-
         final BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
-        mStatsRule.setTime(1000, 1000);
-        batteryStats.resetAllStatsCmdLocked();
-        batteryStats.setNoAutoReset(true);
         batteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING, 100,
                 /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0,
                 1_000_000, 1_000_000, 1_000_000);
@@ -58,8 +52,11 @@
                 /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0,
                 2_000_000, 2_000_000, 2_000_000);
 
+        BatteryChargeCalculator calculator = new BatteryChargeCalculator();
         BatteryUsageStats batteryUsageStats = mStatsRule.apply(calculator);
 
+        assertThat(batteryUsageStats.getConsumedPower())
+                .isWithin(PRECISION).of(380.0);
         assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(10);
         assertThat(batteryUsageStats.getDischargedPowerRange().getLower())
                 .isWithin(PRECISION).of(360.0);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
index 41fe372..1a6408f 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsRule.java
@@ -23,10 +23,10 @@
 
 import android.content.Context;
 import android.net.NetworkStats;
+import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
 import android.util.SparseArray;
@@ -186,6 +186,16 @@
         return mBatteryUsageStats;
     }
 
+    public BatteryConsumer getDeviceBatteryConsumer() {
+        return mBatteryUsageStats.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+    }
+
+    public BatteryConsumer getAppsBatteryConsumer() {
+        return mBatteryUsageStats.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+    }
+
     public UidBatteryConsumer getUidBatteryConsumer(int uid) {
         for (UidBatteryConsumer ubc : mBatteryUsageStats.getUidBatteryConsumers()) {
             if (ubc.getUid() == uid) {
@@ -195,16 +205,6 @@
         return null;
     }
 
-    public SystemBatteryConsumer getSystemBatteryConsumer(
-            @SystemBatteryConsumer.DrainType int drainType) {
-        for (SystemBatteryConsumer sbc : mBatteryUsageStats.getSystemBatteryConsumers()) {
-            if (sbc.getDrainType() == drainType) {
-                return sbc;
-            }
-        }
-        return null;
-    }
-
     public UserBatteryConsumer getUserBatteryConsumer(int userId) {
         for (UserBatteryConsumer ubc : mBatteryUsageStats.getUserBatteryConsumers()) {
             if (ubc.getUserId() == userId) {
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 55302bc..127cea8 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -23,7 +23,6 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryUsageStats;
 import android.os.Parcel;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 
 import androidx.test.filters.SmallTest;
@@ -71,7 +70,6 @@
                         .setDischargePercentage(20)
                         .setDischargedPowerRange(1000, 2000)
                         .setStatsStartTimestamp(1000);
-
         builder.getOrCreateUidBatteryConsumerBuilder(batteryStatsUid)
                 .setPackageWithHighestDrain("foo")
                 .setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000)
@@ -87,7 +85,8 @@
                 .setUsageDurationForCustomComponentMillis(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 800);
 
-        builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_CAMERA)
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                 .setConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CPU, 10100)
                 .setConsumedPowerForCustomComponent(
@@ -95,17 +94,25 @@
                 .setUsageDurationMillis(
                         BatteryConsumer.POWER_COMPONENT_CPU, 10300)
                 .setUsageDurationForCustomComponentMillis(
-                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400)
-                .setPowerConsumedByApps(20000);
+                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 10400);
+
+        builder.getAggregateBatteryConsumerBuilder(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                .setConsumedPower(30000)
+                .setConsumedPower(
+                        BatteryConsumer.POWER_COMPONENT_CPU, 20100)
+                .setConsumedPowerForCustomComponent(
+                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
+                .setUsageDurationMillis(
+                        BatteryConsumer.POWER_COMPONENT_CPU, 20300)
+                .setUsageDurationForCustomComponentMillis(
+                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20400);
 
         return builder.build();
     }
 
     public void validateBatteryUsageStats(BatteryUsageStats batteryUsageStats) {
-        // Camera: (10100 + 10200) - 20000 (consumed by apps) = 300
-        // App: 300 + 400 + 500 = 1200
-        // Total: 1500
-        assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(1500);
+        assertThat(batteryUsageStats.getConsumedPower()).isEqualTo(30000);
         assertThat(batteryUsageStats.getDischargePercentage()).isEqualTo(20);
         assertThat(batteryUsageStats.getDischargedPowerRange().getLower()).isEqualTo(1000);
         assertThat(batteryUsageStats.getDischargedPowerRange().getUpper()).isEqualTo(2000);
@@ -139,28 +146,32 @@
             }
         }
 
-        final List<SystemBatteryConsumer> systemBatteryConsumers =
-                batteryUsageStats.getSystemBatteryConsumers();
-        for (SystemBatteryConsumer systemBatteryConsumer : systemBatteryConsumers) {
-            if (systemBatteryConsumer.getDrainType() == SystemBatteryConsumer.DRAIN_TYPE_CAMERA) {
-                assertThat(systemBatteryConsumer.getConsumedPower(
-                        BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100);
-                assertThat(systemBatteryConsumer.getConsumedPowerForCustomComponent(
-                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200);
-                assertThat(systemBatteryConsumer.getUsageDurationMillis(
-                        BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10300);
-                assertThat(systemBatteryConsumer.getUsageDurationForCustomComponentMillis(
-                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10400);
-                assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(20300);
-                assertThat(systemBatteryConsumer.getPowerConsumedByApps()).isEqualTo(20000);
-                assertThat(systemBatteryConsumer.getUsageDurationMillis())
-                        .isEqualTo(10400); // max
-                assertThat(systemBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
-                assertThat(systemBatteryConsumer.getCustomPowerComponentName(
-                        BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
-            } else {
-                fail("Unexpected drain type " + systemBatteryConsumer.getDrainType());
-            }
-        }
+        final BatteryConsumer appsBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS);
+        assertThat(appsBatteryConsumer.getConsumedPower(
+                BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10100);
+        assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10200);
+        assertThat(appsBatteryConsumer.getUsageDurationMillis(
+                BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(10300);
+        assertThat(appsBatteryConsumer.getUsageDurationForCustomComponentMillis(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(10400);
+        assertThat(appsBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
+        assertThat(appsBatteryConsumer.getCustomPowerComponentName(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
+
+        final BatteryConsumer deviceBatteryConsumer = batteryUsageStats.getAggregateBatteryConsumer(
+                BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
+        assertThat(deviceBatteryConsumer.getConsumedPower(
+                BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20100);
+        assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20200);
+        assertThat(deviceBatteryConsumer.getUsageDurationMillis(
+                BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(20300);
+        assertThat(deviceBatteryConsumer.getUsageDurationForCustomComponentMillis(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo(20400);
+        assertThat(deviceBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
+        assertThat(deviceBatteryConsumer.getCustomPowerComponentName(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
index 8723195..2de621c 100644
--- a/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BluetoothPowerCalculatorTest.java
@@ -24,7 +24,6 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -65,13 +64,18 @@
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                0.11388, 6000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
                 0.24722, 15000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertBluetoothPowerAndDuration(
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
-                0.51944, 9000, 0.51944, 0.36111, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                mStatsRule.getDeviceBatteryConsumer(),
+                0.40555, 24000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                0.36111, 21000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -93,13 +97,18 @@
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                0.1, 6000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
                 0.2, 15000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertBluetoothPowerAndDuration(
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
-                0.45, 9000, 0.45, 0.3, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+                mStatsRule.getDeviceBatteryConsumer(),
+                0.35, 24000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                0.3, 21000, BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -118,13 +127,18 @@
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder().includePowerModels().build(),
                 calculator);
 
-        assertThat(mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID)).isNull();
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
+                0.10378, 3583, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
                 0.22950, 8416, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
         assertBluetoothPowerAndDuration(
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_BLUETOOTH),
-                0.43712, 3584, 0.43712, 0.33329, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                mStatsRule.getDeviceBatteryConsumer(),
+                0.33333, 12000, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+        assertBluetoothPowerAndDuration(
+                mStatsRule.getAppsBatteryConsumer(),
+                0.33329, 11999, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
     private void setDurationsAndPower(
@@ -151,16 +165,4 @@
 
         assertThat(usageDurationMillis).isEqualTo(durationMs);
     }
-
-    private void assertBluetoothPowerAndDuration(@Nullable SystemBatteryConsumer batteryConsumer,
-            double powerMah, int durationMs, double consumedPower, double attributedPower,
-            @BatteryConsumer.PowerModel int powerModel) {
-        assertBluetoothPowerAndDuration(batteryConsumer, powerMah, durationMs, powerModel);
-
-        assertThat(batteryConsumer.getConsumedPower())
-                .isWithin(PRECISION).of(consumedPower);
-
-        assertThat(batteryConsumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(attributedPower);
-    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
index 61eb173..c40d8a0 100644
--- a/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CameraPowerCalculatorTest.java
@@ -42,9 +42,9 @@
 
     @Test
     public void testTimerBasedModel() {
-        BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
-        uidStats.noteCameraTurnedOnLocked(1000);
-        uidStats.noteCameraTurnedOffLocked(2000);
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        stats.noteCameraOnLocked(APP_UID, 1000, 1000);
+        stats.noteCameraOffLocked(APP_UID, 2000, 2000);
 
         CameraPowerCalculator calculator =
                 new CameraPowerCalculator(mStatsRule.getPowerProfile());
@@ -56,5 +56,19 @@
                 .isEqualTo(1000);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
                 .isWithin(PRECISION).of(0.1);
+
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(1000);
+        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.1);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isEqualTo(1000);
+        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CAMERA))
+                .isWithin(PRECISION).of(0.1);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
index 1a99fb0f..152d246 100644
--- a/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CpuPowerCalculatorTest.java
@@ -160,6 +160,18 @@
         assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
         assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(3.76455);
+        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(3.76455);
+        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -224,5 +236,17 @@
         assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
         assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
+
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(10.62949);
+        assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isWithin(PRECISION).of(10.62949);
+        assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
index f011117..f8c2bc6 100644
--- a/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/CustomMeasuredPowerCalculatorTest.java
@@ -20,7 +20,6 @@
 
 import android.os.BatteryConsumer;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.util.SparseLongArray;
 
@@ -68,12 +67,19 @@
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
                 .isWithin(PRECISION).of(33.33333);
 
-        SystemBatteryConsumer systemConsumer = mStatsRule.getSystemBatteryConsumer(
-                SystemBatteryConsumer.DRAIN_TYPE_CUSTOM);
-        assertThat(systemConsumer.getConsumedPowerForCustomComponent(
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
                 .isWithin(PRECISION).of(27.77777);
-        assertThat(systemConsumer.getConsumedPowerForCustomComponent(
+        assertThat(deviceBatteryConsumer.getConsumedPowerForCustomComponent(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
+                .isWithin(PRECISION).of(55.55555);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
+                BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID))
+                .isWithin(PRECISION).of(27.77777);
+        assertThat(appsBatteryConsumer.getConsumedPowerForCustomComponent(
                 BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + 1))
                 .isWithin(PRECISION).of(55.55555);
     }
diff --git a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
index 7ea799f..1964430 100644
--- a/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/GnssPowerCalculatorTest.java
@@ -62,6 +62,18 @@
                 .isWithin(PRECISION).of(0.1);
         assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isWithin(PRECISION).of(0.1);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isWithin(PRECISION).of(0.1);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -97,5 +109,17 @@
                 .isWithin(PRECISION).of(5.55555);
         assertThat(consumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isWithin(PRECISION).of(8.333333);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isWithin(PRECISION).of(8.333333);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
index 2331eeb..67b1e51 100644
--- a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.BatteryConsumer;
-import android.os.SystemBatteryConsumer;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -46,11 +45,16 @@
 
         mStatsRule.apply(calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_IDLE);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE))
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE))
                 .isEqualTo(3000);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
                 .isWithin(PRECISION).of(0.7);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_IDLE))
+                .isEqualTo(0);
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
+                .isWithin(PRECISION).of(0);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
index 94e760a..4868d6a 100644
--- a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
@@ -19,7 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.os.BatteryConsumer;
-import android.os.SystemBatteryConsumer;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -53,8 +52,7 @@
 
         mStatsRule.apply(calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MEMORY);
+        BatteryConsumer consumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MEMORY))
                 .isEqualTo(3000);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY))
diff --git a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
index 5b84a1b..48a1da1 100644
--- a/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MobileRadioPowerCalculatorTest.java
@@ -27,7 +27,6 @@
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
@@ -100,22 +99,23 @@
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(2.2444);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertThat(consumer.getConsumedPower())
-                .isWithin(PRECISION).of(2.2444);
-        assertThat(consumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(0.8);
-
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(0.8);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(2.2444);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.8);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -159,21 +159,22 @@
 
         mStatsRule.apply(calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MOBILE_RADIO);
-
-        // 100000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) + 1.53934 (apps)= 4.31711 mAh
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(4.31711);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-        assertThat(consumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(1.53934);
-
         UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.53934);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(4.31711);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.53934);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index 93c7106..4c29c20 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -21,7 +21,6 @@
 import android.app.ActivityManager;
 import android.os.BatteryConsumer;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.view.Display;
 
@@ -82,21 +81,6 @@
 
         mStatsRule.apply(calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(80 * MINUTE_IN_MS);
-
-        // 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s)  = 166.66666 mAh
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isWithin(PRECISION).of(166.66666);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-        assertThat(consumer.getConsumedPower())
-                .isWithin(PRECISION).of(166.66666);
-        assertThat(consumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(166.66666);
-
         UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
         assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(20 * MINUTE_IN_MS);
@@ -120,6 +104,25 @@
                 .isWithin(PRECISION).of(101.85185);
         assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(80 * MINUTE_IN_MS);
+
+        // 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s)  = 166.66666 mAh
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(166.66666);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(80 * MINUTE_IN_MS);
+
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(166.66666);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
     @Test
@@ -151,19 +154,6 @@
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        SystemBatteryConsumer consumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
-        assertThat(consumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(80 * MINUTE_IN_MS);
-        assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isWithin(PRECISION).of(92.0);
-        assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertThat(consumer.getConsumedPower())
-                .isWithin(PRECISION).of(92.0);
-        assertThat(consumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(92.0);
-
         UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
         assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(20 * MINUTE_IN_MS);
@@ -185,6 +175,22 @@
                 .isWithin(PRECISION).of(69.0);
         assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(80 * MINUTE_IN_MS);
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(92);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(80 * MINUTE_IN_MS);
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isWithin(PRECISION).of(92);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     private void setProcState(int uid, int procState, boolean resumed, long realtimeMs,
diff --git a/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java
index 74235b2..7563e39 100644
--- a/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SensorPowerCalculatorTest.java
@@ -59,11 +59,11 @@
         when(sensorManager.getSensorList(Sensor.TYPE_ALL))
                 .thenReturn(List.of(sensor1, sensor2));
 
-        BatteryStatsImpl.Uid uidStats = mStatsRule.getUidStats(APP_UID);
-        uidStats.noteStartSensor(SENSOR_HANDLE_1, 1000);
-        uidStats.noteStopSensor(SENSOR_HANDLE_1, 2000);
-        uidStats.noteStartSensor(SENSOR_HANDLE_2, 3000);
-        uidStats.noteStopSensor(SENSOR_HANDLE_2, 5000);
+        final BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+        stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_1, 1000, 1000);
+        stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_1, 2000, 2000);
+        stats.noteStartSensorLocked(APP_UID, SENSOR_HANDLE_2, 3000, 3000);
+        stats.noteStopSensorLocked(APP_UID, SENSOR_HANDLE_2, 5000, 5000);
 
         SensorPowerCalculator calculator = new SensorPowerCalculator(sensorManager);
 
@@ -74,6 +74,14 @@
                 .isEqualTo(3000);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS))
                 .isWithin(PRECISION).of(0.5);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS))
+                .isWithin(PRECISION).of(0.5);
+
+        BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SENSORS))
+                .isWithin(PRECISION).of(0.5);
     }
 
     private Sensor createSensor(int handle, int type, double power) {
diff --git a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
index 58e2513..cd45060 100644
--- a/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/SystemServicePowerCalculatorTest.java
@@ -133,6 +133,12 @@
         assertThat(mStatsRule.getUidBatteryConsumer(Process.SYSTEM_UID)
                 .getConsumedPower(BatteryConsumer.POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS))
                 .isWithin(PRECISION).of(-18.888888);
+        assertThat(mStatsRule.getDeviceBatteryConsumer()
+                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
+                .isWithin(PRECISION).of(18.888888);
+        assertThat(mStatsRule.getAppsBatteryConsumer()
+                .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES))
+                .isWithin(PRECISION).of(18.888888);
     }
 
     private static class MockKernelCpuUidFreqTimeReader extends
diff --git a/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
index fa0dbc7..ae61d31 100644
--- a/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/VideoPowerCalculatorTest.java
@@ -56,5 +56,19 @@
                 .isEqualTo(1000);
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO))
                 .isWithin(PRECISION).of(0.1);
+
+        final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO))
+                .isEqualTo(1000);
+        assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO))
+                .isWithin(PRECISION).of(0.1);
+
+        final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appsBatteryConsumer
+                .getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_VIDEO))
+                .isEqualTo(1000);
+        assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_VIDEO))
+                .isWithin(PRECISION).of(0.1);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
index 9d3ed55..82830f2 100644
--- a/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WakelockPowerCalculatorTest.java
@@ -72,5 +72,15 @@
                 .isEqualTo(5000);
         assertThat(osConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
                 .isWithin(PRECISION).of(0.5);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+                .isEqualTo(6000);
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+                .isWithin(PRECISION).of(0.6);
+
+        BatteryConsumer appConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WAKELOCK))
+                .isWithin(PRECISION).of(0.6);
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
index 4a7cf1e..fc44ddc 100644
--- a/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/WifiPowerCalculatorTest.java
@@ -25,7 +25,6 @@
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
 import android.os.Process;
-import android.os.SystemBatteryConsumer;
 import android.os.UidBatteryConsumer;
 import android.os.WorkSource;
 import android.os.connectivity.WifiActivityEnergyInfo;
@@ -94,16 +93,19 @@
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
-        SystemBatteryConsumer systemConsumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
-        assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(5577);
-        assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isWithin(PRECISION).of(1.11153);
-        assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isEqualTo(4002);
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isWithin(PRECISION).of(0.86666);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertThat(systemConsumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(0.466333);
+
+        BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isWithin(PRECISION).of(0.866666);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
@@ -125,17 +127,19 @@
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
 
-        SystemBatteryConsumer systemConsumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
-        assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(5577);
-        /* Same ratio as in testPowerControllerBasedModel_nonMeasured but scaled by 1_000_000uC. */
-        assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isWithin(PRECISION).of(1.11153 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
-        assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isEqualTo(4002);
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isWithin(PRECISION).of(0.27777);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-        assertThat(systemConsumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(0.14946);
+
+        BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isWithin(PRECISION).of(0.277777);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
     /** Sets up batterystats object with prepopulated network & timer data for Timer-model tests. */
@@ -168,17 +172,6 @@
                 .isWithin(PRECISION).of(0.8231573);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
-        SystemBatteryConsumer systemConsumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
-        assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(2222);
-        assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isWithin(PRECISION).of(2.575000);
-        assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-        assertThat(systemConsumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(1.69907);
     }
 
     @Test
@@ -200,17 +193,5 @@
                 .isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-
-        SystemBatteryConsumer systemConsumer =
-                mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_WIFI);
-        assertThat(systemConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(2222);
-        /* Same ratio as in testTimerBasedModel_nonMeasured but scaled by 1_000_000uC. */
-        assertThat(systemConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isWithin(PRECISION).of(2.575000 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
-        assertThat(systemConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-        assertThat(systemConsumer.getPowerConsumedByApps())
-                .isWithin(PRECISION).of(0.277777);
     }
 }
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 895cd3e..24d7780 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -864,7 +864,6 @@
         boolean shouldExit = mExitingAnimation;
         mRippleActive = false;
         mExitingAnimation = false;
-        getRipplePaint();
         drawContent(canvas);
         drawPatternedBackground(canvas, cx, cy);
         if (shouldAnimate && mRunningAnimations.size() <= MAX_RIPPLES) {
@@ -928,7 +927,7 @@
             startBackgroundAnimation();
         }
         if (mBackgroundOpacity == 0) return;
-        Paint p = mRipplePaint;
+        Paint p = getRipplePaint();
         float newOpacity = mBackgroundOpacity;
         final int origAlpha = p.getAlpha();
         final int alpha = Math.min((int) (origAlpha * newOpacity + 0.5f), 255);
@@ -957,7 +956,7 @@
     @NonNull
     private RippleAnimationSession.AnimationProperties<Float, Paint> createAnimationProperties(
             float x, float y, float cx, float cy, float w, float h) {
-        Paint p = new Paint(mRipplePaint);
+        Paint p = new Paint(getRipplePaint());
         float radius = getComputedRadius();
         RippleAnimationSession.AnimationProperties<Float, Paint> properties;
         RippleShader shader = new RippleShader();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationCallback.java
index 6749f7e..24e5111 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationCallback.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationCallback.java
@@ -44,8 +44,9 @@
     }
 
     /**
-     * Called when OneHanded animator is updating offset
+     * Called when OneHanded animator is updating position
      */
-    default void onTutorialAnimationUpdate(int offset) {}
+    default void onAnimationUpdate(float xPos, float yPos) {
+    }
 
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
index 25dd3ca..180cceb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@@ -25,8 +25,10 @@
 import android.view.animation.BaseInterpolator;
 import android.window.WindowContainerToken;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -37,6 +39,7 @@
  * Controller class of OneHanded animations (both from and to OneHanded mode).
  */
 public class OneHandedAnimationController {
+    private static final String TAG = "OneHandedAnimationController";
     private static final float FRACTION_START = 0f;
     private static final float FRACTION_END = 1f;
 
@@ -68,17 +71,19 @@
 
     @SuppressWarnings("unchecked")
     OneHandedTransitionAnimator getAnimator(WindowContainerToken token, SurfaceControl leash,
-            Rect startBounds, Rect endBounds) {
+            float startPos, float endPos, Rect displayBounds) {
         final OneHandedTransitionAnimator animator = mAnimatorMap.get(token);
         if (animator == null) {
             mAnimatorMap.put(token, setupOneHandedTransitionAnimator(
-                    OneHandedTransitionAnimator.ofBounds(token, leash, startBounds, endBounds)));
+                    OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos,
+                            displayBounds)));
         } else if (animator.isRunning()) {
-            animator.updateEndValue(endBounds);
+            animator.updateEndValue(endPos);
         } else {
             animator.cancel();
             mAnimatorMap.put(token, setupOneHandedTransitionAnimator(
-                    OneHandedTransitionAnimator.ofBounds(token, leash, startBounds, endBounds)));
+                    OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos,
+                            displayBounds)));
         }
         return mAnimatorMap.get(token);
     }
@@ -147,9 +152,7 @@
         public void onAnimationStart(Animator animation) {
             mCurrentValue = mStartValue;
             mOneHandedAnimationCallbacks.forEach(
-                    (callback) -> {
-                        callback.onOneHandedAnimationStart(this);
-                    }
+                    (callback) -> callback.onOneHandedAnimationStart(this)
             );
         }
 
@@ -159,9 +162,7 @@
             final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
             onEndTransaction(mLeash, tx);
             mOneHandedAnimationCallbacks.forEach(
-                    (callback) -> {
-                        callback.onOneHandedAnimationEnd(tx, this);
-                    }
+                    (callback) -> callback.onOneHandedAnimationEnd(tx, this)
             );
         }
 
@@ -169,9 +170,7 @@
         public void onAnimationCancel(Animator animation) {
             mCurrentValue = mEndValue;
             mOneHandedAnimationCallbacks.forEach(
-                    (callback) -> {
-                        callback.onOneHandedAnimationCancel(this);
-                    }
+                    (callback) -> callback.onOneHandedAnimationCancel(this)
             );
         }
 
@@ -181,12 +180,10 @@
 
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
-            applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(),
-                    animation.getAnimatedFraction());
+            final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
+            applySurfaceControlTransaction(mLeash, tx, animation.getAnimatedFraction());
             mOneHandedAnimationCallbacks.forEach(
-                    (callback) -> {
-                        callback.onTutorialAnimationUpdate(((Rect) mCurrentValue).top);
-                    }
+                    (callback) -> callback.onAnimationUpdate(0f, (float) mCurrentValue)
             );
         }
 
@@ -217,12 +214,8 @@
             return mToken;
         }
 
-        Rect getDestinationBounds() {
-            return (Rect) mEndValue;
-        }
-
-        int getDestinationOffset() {
-            return ((Rect) mEndValue).top - ((Rect) mStartValue).top;
+        float getDestinationOffset() {
+            return ((float) mEndValue - (float) mStartValue);
         }
 
         @TransitionDirection
@@ -259,40 +252,42 @@
         }
 
         @VisibleForTesting
-        static OneHandedTransitionAnimator<Rect> ofBounds(WindowContainerToken token,
-                SurfaceControl leash, Rect startValue, Rect endValue) {
+        static OneHandedTransitionAnimator<Float> ofYOffset(WindowContainerToken token,
+                SurfaceControl leash, float startValue, float endValue, Rect displayBounds) {
 
-            return new OneHandedTransitionAnimator<Rect>(token, leash, new Rect(startValue),
-                    new Rect(endValue)) {
+            return new OneHandedTransitionAnimator<Float>(token, leash, startValue, endValue) {
 
-                private final Rect mTmpRect = new Rect();
+                private final Rect mTmpRect = new Rect(displayBounds);
 
-                private int getCastedFractionValue(float start, float end, float fraction) {
-                    return (int) (start * (1 - fraction) + end * fraction + .5f);
+                private float getCastedFractionValue(float start, float end, float fraction) {
+                    return (start * (1 - fraction) + end * fraction + .5f);
                 }
 
                 @Override
                 void applySurfaceControlTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, float fraction) {
-                    final Rect start = getStartValue();
-                    final Rect end = getEndValue();
+                    final float start = getStartValue();
+                    final float end = getEndValue();
+                    final float currentValue = getCastedFractionValue(start, end, fraction);
                     mTmpRect.set(
-                            getCastedFractionValue(start.left, end.left, fraction),
-                            getCastedFractionValue(start.top, end.top, fraction),
-                            getCastedFractionValue(start.right, end.right, fraction),
-                            getCastedFractionValue(start.bottom, end.bottom, fraction));
-                    setCurrentValue(mTmpRect);
-                    getSurfaceTransactionHelper().crop(tx, leash, mTmpRect)
-                            .round(tx, leash);
+                            mTmpRect.left,
+                            mTmpRect.top + Math.round(currentValue),
+                            mTmpRect.right,
+                            mTmpRect.bottom + Math.round(currentValue));
+                    setCurrentValue(currentValue);
+                    getSurfaceTransactionHelper()
+                            .crop(tx, leash, mTmpRect)
+                            .round(tx, leash)
+                            .translate(tx, leash, currentValue);
                     tx.apply();
                 }
 
                 @Override
                 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
                     getSurfaceTransactionHelper()
-                            .alpha(tx, leash, 1f)
-                            .translate(tx, leash, getEndValue().top - getStartValue().top)
-                            .round(tx, leash);
+                            .crop(tx, leash, mTmpRect)
+                            .round(tx, leash)
+                            .translate(tx, leash, getStartValue());
                     tx.apply();
                 }
             };
@@ -309,4 +304,15 @@
                     * (2.0f * Math.PI) / 4.0f) + 1);
         }
     }
+
+    void dump(@NonNull PrintWriter pw) {
+        final String innerPrefix = "  ";
+        pw.println(TAG + "states: ");
+        pw.print(innerPrefix + "mAnimatorMap=");
+        pw.println(mAnimatorMap);
+
+        if (mSurfaceTransactionHelper != null) {
+            mSurfaceTransactionHelper.dump(pw);
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index 7844c11..b8da37f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -61,6 +61,7 @@
 
     private DisplayLayout mDisplayLayout = new DisplayLayout();
 
+    private float mLastVisualOffset = 0;
     private final Rect mLastVisualDisplayBounds = new Rect();
     private final Rect mDefaultDisplayBounds = new Rect();
     private final OneHandedSettingsUtil mOneHandedSettingsUtil;
@@ -96,8 +97,7 @@
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
                     mAnimationController.removeAnimator(animator.getToken());
                     if (mAnimationController.isAnimatorsConsumed()) {
-                        resetWindowsOffsetInternal(animator.getTransitionDirection());
-                        finishOffset(animator.getDestinationOffset(),
+                        finishOffset((int) animator.getDestinationOffset(),
                                 animator.getTransitionDirection());
                     }
                 }
@@ -107,8 +107,7 @@
                         OneHandedAnimationController.OneHandedTransitionAnimator animator) {
                     mAnimationController.removeAnimator(animator.getToken());
                     if (mAnimationController.isAnimatorsConsumed()) {
-                        resetWindowsOffsetInternal(animator.getTransitionDirection());
-                        finishOffset(animator.getDestinationOffset(),
+                        finishOffset((int) animator.getDestinationOffset(),
                                 animator.getTransitionDirection());
                     }
                 }
@@ -165,16 +164,16 @@
     @Override
     public void unregisterOrganizer() {
         super.unregisterOrganizer();
-        resetWindowsOffset(null);
+        resetWindowsOffset();
     }
 
     /**
      * Handler for display rotation changes by {@link DisplayLayout}
      *
-     * @param context       Any context
-     * @param toRotation    target rotation of the display (after rotating).
-     * @param wct           A task transaction {@link WindowContainerTransaction} from
-     *                      {@link DisplayChangeController} to populate.
+     * @param context    Any context
+     * @param toRotation target rotation of the display (after rotating).
+     * @param wct        A task transaction {@link WindowContainerTransaction} from
+     *                   {@link DisplayChangeController} to populate.
      */
     public void onRotateDisplay(Context context, int toRotation, WindowContainerTransaction wct) {
         if (mDisplayLayout.rotation() == toRotation) {
@@ -186,7 +185,6 @@
             return;
         }
         mDisplayLayout.rotateTo(context.getResources(), toRotation);
-        resetWindowsOffset(wct);
         updateDisplayBounds();
         finishOffset(0, TRANSITION_DIRECTION_EXIT);
     }
@@ -196,38 +194,20 @@
      * Directly perform manipulation/offset on the leash.
      */
     public void scheduleOffset(int xOffset, int yOffset) {
-        final Rect toBounds = new Rect(mDefaultDisplayBounds.left,
-                mDefaultDisplayBounds.top + yOffset,
-                mDefaultDisplayBounds.right,
-                mDefaultDisplayBounds.bottom + yOffset);
-        final Rect fromBounds = getLastVisualDisplayBounds();
+        final float fromPos = mLastVisualOffset;
         final int direction = yOffset > 0
                 ? TRANSITION_DIRECTION_TRIGGER
                 : TRANSITION_DIRECTION_EXIT;
-
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
         mDisplayAreaTokenMap.forEach(
                 (token, leash) -> {
-                    animateWindows(token, leash, fromBounds, toBounds, direction,
+                    animateWindows(token, leash, fromPos, yOffset, direction,
                             mEnterExitAnimationDurationMs);
-                    wct.setBounds(token, toBounds);
-                    wct.setAppBounds(token, toBounds);
                 });
-        applyTransaction(wct);
-    }
-
-    private void resetWindowsOffsetInternal(
-            @OneHandedAnimationController.TransitionDirection int td) {
-        if (td == TRANSITION_DIRECTION_TRIGGER) {
-            return;
-        }
-        final WindowContainerTransaction wct = new WindowContainerTransaction();
-        resetWindowsOffset(wct);
-        applyTransaction(wct);
+        mLastVisualOffset = yOffset;
     }
 
     @VisibleForTesting
-    void resetWindowsOffset(WindowContainerTransaction wct) {
+    void resetWindowsOffset() {
         final SurfaceControl.Transaction tx =
                 mSurfaceControlTransactionFactory.getTransaction();
         mDisplayAreaTokenMap.forEach(
@@ -238,21 +218,20 @@
                         animator.cancel();
                     }
                     tx.setPosition(leash, 0, 0)
-                            .setWindowCrop(leash, -1/* reset */, -1/* reset */);
-                    // DisplayRotationController will applyTransaction() after finish rotating
-                    if (wct != null) {
-                        wct.setBounds(token, null/* reset */);
-                        wct.setAppBounds(token, null/* reset */);
-                    }
+                            .setWindowCrop(leash, -1, -1)
+                            .setCornerRadius(leash, -1);
                 });
         tx.apply();
+        mLastVisualOffset = 0;
+        mLastVisualDisplayBounds.offsetTo(0, 0);
     }
 
-    private void animateWindows(WindowContainerToken token, SurfaceControl leash, Rect fromBounds,
-            Rect toBounds, @OneHandedAnimationController.TransitionDirection int direction,
+    private void animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos,
+            float toPos, @OneHandedAnimationController.TransitionDirection int direction,
             int durationMs) {
         final OneHandedAnimationController.OneHandedTransitionAnimator animator =
-                mAnimationController.getAnimator(token, leash, fromBounds, toBounds);
+                mAnimationController.getAnimator(token, leash, fromPos, toPos,
+                        mLastVisualDisplayBounds);
         if (animator != null) {
             animator.setTransitionDirection(direction)
                     .addOneHandedAnimationCallback(mOneHandedAnimationCallback)
@@ -265,10 +244,13 @@
     }
 
     @VisibleForTesting
-    void finishOffset(int offset,
-            @OneHandedAnimationController.TransitionDirection int direction) {
-        mLastVisualDisplayBounds.offsetTo(0,
-                direction == TRANSITION_DIRECTION_TRIGGER ? offset : 0);
+    void finishOffset(int offset, @OneHandedAnimationController.TransitionDirection int direction) {
+        if (direction == TRANSITION_DIRECTION_EXIT) {
+            // We must do this to ensure reset property for leash when exit one handed mode
+            resetWindowsOffset();
+        }
+        mLastVisualOffset = direction == TRANSITION_DIRECTION_TRIGGER ? offset : 0;
+        mLastVisualDisplayBounds.offsetTo(0, Math.round(mLastVisualOffset));
         for (int i = mTransitionCallbacks.size() - 1; i >= 0; i--) {
             final OneHandedTransitionCallback cb = mTransitionCallbacks.get(i);
             cb.onStartTransition(false /* isTransitioning */);
@@ -285,7 +267,7 @@
      *
      * @return Rect latest finish_offset
      */
-    public Rect getLastVisualDisplayBounds() {
+    private Rect getLastVisualDisplayBounds() {
         return mLastVisualDisplayBounds;
     }
 
@@ -332,5 +314,11 @@
         pw.println(mDefaultDisplayBounds);
         pw.print(innerPrefix + "mLastVisualDisplayBounds=");
         pw.println(mLastVisualDisplayBounds);
+        pw.print(innerPrefix + "mLastVisualOffset=");
+        pw.println(mLastVisualOffset);
+
+        if (mAnimationController != null) {
+            mAnimationController.dump(pw);
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSurfaceTransactionHelper.java
index e7010db..e9048c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSurfaceTransactionHelper.java
@@ -21,18 +21,28 @@
 import android.graphics.Rect;
 import android.view.SurfaceControl;
 
+import androidx.annotation.NonNull;
+
 import com.android.wm.shell.R;
 
+import java.io.PrintWriter;
+
 /**
  * Abstracts the common operations on {@link SurfaceControl.Transaction} for OneHanded transition.
  */
 public class OneHandedSurfaceTransactionHelper {
+    private static final String TAG = "OneHandedSurfaceTransactionHelper";
+
     private final boolean mEnableCornerRadius;
     private final float mCornerRadius;
+    private final float mCornerRadiusAdjustment;
 
     public OneHandedSurfaceTransactionHelper(Context context) {
         final Resources res = context.getResources();
-        mCornerRadius = res.getDimension(com.android.internal.R.dimen.rounded_corner_radius);
+        mCornerRadiusAdjustment = res.getDimension(
+                com.android.internal.R.dimen.rounded_corner_radius_adjustment);
+        mCornerRadius = res.getDimension(com.android.internal.R.dimen.rounded_corner_radius)
+                - mCornerRadiusAdjustment;
         mEnableCornerRadius = res.getBoolean(R.bool.config_one_handed_enable_round_corner);
     }
 
@@ -48,25 +58,13 @@
     }
 
     /**
-     * Operates the alpha on a given transaction and leash
-     *
-     * @return same {@link OneHandedSurfaceTransactionHelper} instance for method chaining
-     */
-    OneHandedSurfaceTransactionHelper alpha(SurfaceControl.Transaction tx, SurfaceControl leash,
-            float alpha) {
-        tx.setAlpha(leash, alpha);
-        return this;
-    }
-
-    /**
      * Operates the crop (setMatrix) on a given transaction and leash
      *
      * @return same {@link OneHandedSurfaceTransactionHelper} instance for method chaining
      */
     OneHandedSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect destinationBounds) {
-        tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
-                .setPosition(leash, destinationBounds.left, destinationBounds.top);
+        tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height());
         return this;
     }
 
@@ -85,4 +83,15 @@
     interface SurfaceControlTransactionFactory {
         SurfaceControl.Transaction getTransaction();
     }
+
+    void dump(@NonNull PrintWriter pw) {
+        final String innerPrefix = "  ";
+        pw.println(TAG + "states: ");
+        pw.print(innerPrefix + "mEnableCornerRadius=");
+        pw.println(mEnableCornerRadius);
+        pw.print(innerPrefix + "mCornerRadiusAdjustment=");
+        pw.println(mCornerRadiusAdjustment);
+        pw.print(innerPrefix + "mCornerRadius=");
+        pw.println(mCornerRadius);
+    }
 }
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 d55359c..e8cee8a 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
@@ -78,16 +78,21 @@
 
     private final OneHandedAnimationCallback mAnimationCallback = new OneHandedAnimationCallback() {
         @Override
-        public void onTutorialAnimationUpdate(int offset) {
-            onAnimationUpdate(offset);
+        public void onAnimationUpdate(float xPos, float yPos) {
+            if (!canShowTutorial()) {
+                return;
+            }
+            mTargetViewContainer.setVisibility(View.VISIBLE);
+            mTargetViewContainer.setTransitionGroup(true);
+            mTargetViewContainer.setTranslationY(yPos - mTargetViewContainer.getHeight());
         }
 
         @Override
         public void onOneHandedAnimationStart(
                 OneHandedAnimationController.OneHandedTransitionAnimator animator) {
-            final Rect startValue = (Rect) animator.getStartValue();
+            final float startValue = (float) animator.getStartValue();
             if (mTriggerState == ONE_HANDED_TRIGGER_STATE.UNSET) {
-                mTriggerState = (startValue.top == 0)
+                mTriggerState = (startValue == 0f)
                         ? ONE_HANDED_TRIGGER_STATE.ENTERING : ONE_HANDED_TRIGGER_STATE.EXITING;
                 if (mCanShowTutorial && mTriggerState == ONE_HANDED_TRIGGER_STATE.ENTERING) {
                     attachTurtorialTarget();
@@ -239,15 +244,6 @@
         return true;
     }
 
-    private void onAnimationUpdate(float value) {
-        if (!canShowTutorial()) {
-            return;
-        }
-        mTargetViewContainer.setVisibility(View.VISIBLE);
-        mTargetViewContainer.setTransitionGroup(true);
-        mTargetViewContainer.setTranslationY(value - mTargetViewContainer.getHeight());
-    }
-
     /**
      * onConfigurationChanged events for updating tutorial text.
      * @param newConfig
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index efaa269..f7160e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -452,6 +452,10 @@
             // Make sure the main stage is active.
             mMainStage.activate(getMainStageBounds(), wct);
             mSideStage.setBounds(getSideStageBounds(), wct);
+            // Reorder side stage to the top whenever there's a new child task appeared in side
+            // stage. This is needed to prevent main stage occludes side stage and makes main stage
+            // flipping between fullscreen and multi-window windowing mode.
+            wct.reorder(mSideStage.mRootTaskInfo.token, true);
             mTaskOrganizer.applyTransaction(wct);
         }
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
index a8feb04..af11b7e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedAnimationControllerTest.java
@@ -26,8 +26,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.common.ShellExecutor;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,8 +41,6 @@
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class OneHandedAnimationControllerTest extends OneHandedTestCase {
-    private static final int TEST_BOUNDS_WIDTH = 1000;
-    private static final int TEST_BOUNDS_HEIGHT = 1000;
 
     OneHandedAnimationController mOneHandedAnimationController;
 
@@ -52,9 +48,7 @@
     private SurfaceControl mMockLeash;
     @Mock
     private WindowContainerToken mMockToken;
-
-    @Mock
-    private ShellExecutor mMainExecutor;
+    private Rect mDisplayBounds = new Rect();
 
     @Before
     public void setUp() throws Exception {
@@ -64,12 +58,10 @@
 
     @Test
     public void testGetAnimator_withSameBounds_returnAnimator() {
-        final Rect originalBounds = new Rect(0, 0, TEST_BOUNDS_WIDTH, TEST_BOUNDS_HEIGHT);
-        final Rect destinationBounds = originalBounds;
-        destinationBounds.offset(0, 300);
+        final float yOffset = 300;
         final OneHandedAnimationController.OneHandedTransitionAnimator animator =
                 mOneHandedAnimationController
-                        .getAnimator(mMockToken, mMockLeash, originalBounds, destinationBounds);
+                        .getAnimator(mMockToken, mMockLeash, 0, yOffset, mDisplayBounds);
 
         assertNotNull(animator);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
index eb731d2..a27ed11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java
@@ -116,7 +116,9 @@
         mDisplayLayout = new DisplayLayout(mContext, mDisplay);
         mDisplayAreaInfo = new DisplayAreaInfo(mToken, DEFAULT_DISPLAY, FEATURE_ONE_HANDED);
         mDisplayAreaInfo.configuration.orientation = Configuration.ORIENTATION_PORTRAIT;
-        when(mMockAnimationController.getAnimator(any(), any(), any(), any())).thenReturn(null);
+        when(mMockAnimationController.getAnimator(any(), any(), anyFloat(), anyFloat(),
+                any())).thenReturn(
+                null);
         when(mMockDisplayController.getDisplay(anyInt())).thenReturn(mDisplay);
         when(mMockSurfaceTransactionHelper.translate(any(), any(), anyFloat())).thenReturn(
                 mMockSurfaceTransactionHelper);
@@ -164,7 +166,8 @@
                         info.getDisplayAreaInfo(),
                         info.getLeash()));
 
-        verify(mMockAnimationController, never()).getAnimator(any(), any(), any(), any());
+        verify(mMockAnimationController, never()).getAnimator(any(), any(), anyFloat(), anyFloat(),
+                any());
     }
 
     @Test
@@ -189,7 +192,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -200,7 +203,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -213,7 +216,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -226,7 +229,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -239,7 +242,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -252,7 +255,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -265,7 +268,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -278,7 +281,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -289,8 +292,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
-                mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
     }
 
@@ -301,7 +303,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -314,8 +316,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_180,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
-                mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
     }
 
@@ -328,7 +329,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_0,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -341,8 +342,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
-                mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
     }
 
@@ -355,7 +355,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -368,8 +368,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_270,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset(
-                mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer, never()).finishOffset(anyInt(), anyInt());
     }
 
@@ -382,8 +381,7 @@
         mSpiedDisplayAreaOrganizer.onRotateDisplay(mContext, Surface.ROTATION_90,
                 mMockWindowContainerTransaction);
 
-        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset(
-                mMockWindowContainerTransaction);
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
         verify(mSpiedDisplayAreaOrganizer).finishOffset(anyInt(), anyInt());
     }
 
@@ -406,4 +404,18 @@
 
         assertThat(mSpiedDisplayAreaOrganizer.getLastDisplayBounds()).isEqualTo(testBounds);
     }
+
+    @Test
+    public void testExit_must_resetWindowsOffset() {
+        mSpiedDisplayAreaOrganizer.finishOffset(0, TRANSITION_DIRECTION_EXIT);
+
+        verify(mSpiedDisplayAreaOrganizer).resetWindowsOffset();
+    }
+
+    @Test
+    public void testTrigger_not_resetWindowsOffset() {
+        mSpiedDisplayAreaOrganizer.finishOffset(0, TRANSITION_DIRECTION_TRIGGER);
+
+        verify(mSpiedDisplayAreaOrganizer, never()).resetWindowsOffset();
+    }
 }
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 1e90b7c..17c1404 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -816,6 +816,11 @@
     uint32_t                    mSourceResourceId;
 };
 
+static inline bool operator==(const android::ResXMLParser::ResXMLPosition& lhs,
+                              const android::ResXMLParser::ResXMLPosition& rhs) {
+  return lhs.curNode == rhs.curNode;
+}
+
 class DynamicRefTable;
 
 /**
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index c1d6725..5952479 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -360,6 +360,9 @@
         }
     }
 
+    private static volatile LocationEnabledCache sLocationEnabledCache =
+            new LocationEnabledCache(4);
+
     @GuardedBy("sLocationListeners")
     private static final WeakHashMap<LocationListener, WeakReference<LocationListenerTransport>>
             sLocationListeners = new WeakHashMap<>();
@@ -386,20 +389,6 @@
     final Context mContext;
     final ILocationManager mService;
 
-    private volatile PropertyInvalidatedCache<Integer, Boolean> mLocationEnabledCache =
-            new PropertyInvalidatedCache<Integer, Boolean>(
-                    4,
-                    CACHE_KEY_LOCATION_ENABLED_PROPERTY) {
-                @Override
-                protected Boolean recompute(Integer userHandle) {
-                    try {
-                        return mService.isLocationEnabledForUser(userHandle);
-                    } catch (RemoteException e) {
-                        throw e.rethrowFromSystemServer();
-                    }
-                }
-            };
-
     /**
      * @hide
      */
@@ -533,7 +522,7 @@
      * @return true if location is enabled and false if location is disabled.
      */
     public boolean isLocationEnabled() {
-        return isLocationEnabledForUser(Process.myUserHandle());
+        return isLocationEnabledForUser(mContext.getUser());
     }
 
     /**
@@ -546,12 +535,17 @@
      */
     @SystemApi
     public boolean isLocationEnabledForUser(@NonNull UserHandle userHandle) {
-        PropertyInvalidatedCache<Integer, Boolean> cache = mLocationEnabledCache;
-        if (cache != null) {
-            return cache.query(userHandle.getIdentifier());
+        // skip the cache for any "special" user ids - special ids like CURRENT_USER may change
+        // their meaning over time and should never be in the cache. we could resolve the special
+        // user ids here, but that would require an x-process call anyways, and the whole point of
+        // the cache is to avoid x-process calls.
+        if (userHandle.getIdentifier() >= 0) {
+            PropertyInvalidatedCache<Integer, Boolean> cache = sLocationEnabledCache;
+            if (cache != null) {
+                return cache.query(userHandle.getIdentifier());
+            }
         }
 
-        // fallback if cache is disabled
         try {
             return mService.isLocationEnabledForUser(userHandle.getIdentifier());
         } catch (RemoteException e) {
@@ -3004,7 +2998,7 @@
             ListenerExecutor, CancellationSignal.OnCancelListener {
 
         private final Executor mExecutor;
-        private volatile @Nullable Consumer<Location> mConsumer;
+        volatile @Nullable Consumer<Location> mConsumer;
 
         GetCurrentLocationTransport(Executor executor, Consumer<Location> consumer,
                 @Nullable CancellationSignal cancellationSignal) {
@@ -3465,6 +3459,37 @@
         }
     }
 
+    private static class LocationEnabledCache extends PropertyInvalidatedCache<Integer, Boolean> {
+
+        // this is not loaded immediately because this class is created as soon as LocationManager
+        // is referenced for the first time, and within the system server, the ILocationManager
+        // service may not have been loaded yet at that time.
+        private @Nullable ILocationManager mManager;
+
+        LocationEnabledCache(int numEntries) {
+            super(numEntries, CACHE_KEY_LOCATION_ENABLED_PROPERTY);
+        }
+
+        @Override
+        protected Boolean recompute(Integer userId) {
+            Preconditions.checkArgument(userId >= 0);
+
+            if (mManager == null) {
+                try {
+                    mManager = getService();
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            }
+
+            try {
+                return mManager.isLocationEnabledForUser(userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
     /**
      * @hide
      */
@@ -3475,7 +3500,7 @@
     /**
      * @hide
      */
-    public void disableLocalLocationEnabledCaches() {
-        mLocationEnabledCache = null;
+    public static void disableLocalLocationEnabledCaches() {
+        sLocationEnabledCache = null;
     }
 }
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 0c5eaa7..df81603 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -1525,7 +1525,7 @@
 
     /**
      * Get the {@link NetworkCapabilities} for the given {@link Network}.  This
-     * will return {@code null} if the network is unknown.
+     * will return {@code null} if the network is unknown or if the |network| argument is null.
      *
      * This will remove any location sensitive data in {@link TransportInfo} embedded in
      * {@link NetworkCapabilities#getTransportInfo()}. Some transport info instances like
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java
index 9c82907..778537b 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.java
@@ -39,11 +39,4 @@
         setLayoutResource(R.layout.top_intro_preference);
         setSelectable(false);
     }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder holder) {
-        super.onBindViewHolder(holder);
-        holder.setDividerAllowedAbove(true);
-        holder.setDividerAllowedBelow(true);
-    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index f180776..f046f06 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -2027,6 +2027,7 @@
                 }
             };
 
+    /* For the Storage Settings which shows category of app types. */
     public static final AppFilter FILTER_OTHER_APPS =
             new AppFilter() {
                 @Override
@@ -2046,4 +2047,21 @@
                     return !isCategorized;
                 }
             };
+
+    /* For the Storage Settings which shows category of file types. */
+    public static final AppFilter FILTER_APPS_EXCEPT_GAMES =
+            new AppFilter() {
+                @Override
+                public void init() {
+                }
+
+                @Override
+                public boolean filterApp(AppEntry entry) {
+                    boolean isCategorized;
+                    synchronized (entry) {
+                        isCategorized = FILTER_GAMES.filterApp(entry);
+                    }
+                    return !isCategorized;
+                }
+            };
 }
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index d9ac262..f1e1e7d 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -138,6 +138,34 @@
     }
 
     @Test
+    public void testAppsExceptGamesFilterRejectsGame() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_GAME;
+
+        assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isFalse();
+    }
+
+    @Test
+    public void testAppsExceptGamesFilterAcceptsImage() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_IMAGE;
+
+        assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
+    }
+
+    @Test
+    public void testAppsExceptGamesFilterAcceptsVideo() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_VIDEO;
+
+        assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
+    }
+
+    @Test
+    public void testAppsExceptGamesFilterAcceptsAudio() {
+        mEntry.info.category = ApplicationInfo.CATEGORY_AUDIO;
+
+        assertThat(ApplicationsState.FILTER_APPS_EXCEPT_GAMES.filterApp(mEntry)).isTrue();
+    }
+
+    @Test
     public void testDownloadAndLauncherAndInstantAcceptsCorrectApps() {
         // should include instant apps
         mEntry.isHomeApp = false;
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 20273d0..1cf0c5f 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -17,6 +17,7 @@
 import android.view.RemoteAnimationTarget
 import android.view.SyncRtSurfaceTransactionApplier
 import android.view.View
+import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.animation.AnimationUtils
 import android.view.animation.PathInterpolator
@@ -112,7 +113,7 @@
     @PublishedApi
     internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
         if (Looper.myLooper() != Looper.getMainLooper()) {
-            this.getRootView().context.mainExecutor.execute {
+            this.launchContainer.context.mainExecutor.execute {
                 this.onIntentStarted(willAnimate)
             }
         } else {
@@ -166,15 +167,19 @@
         }
 
         /**
-         * Return the root [View] that contains the view that started the intent and will be
-         * animating together with the window.
+         * The container in which the view that started the intent will be animating together with
+         * the opening window.
          *
-         * This view will be used to:
+         * This will be used to:
          *  - Get the associated [Context].
          *  - Compute whether we are expanding fully above the current window.
          *  - Apply surface transactions in sync with RenderThread.
+         *
+         * This container can be changed to force this [Controller] to animate the expanding view
+         * inside a different location, for instance to ensure correct layering during the
+         * animation.
          */
-        fun getRootView(): View
+        var launchContainer: ViewGroup
 
         /**
          * Return the [State] of the view that will be animated. We will animate from this state to
@@ -272,9 +277,9 @@
 
     @VisibleForTesting
     inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
-        private val rootView = controller.getRootView()
-        @PublishedApi internal val context = rootView.context
-        private val transactionApplier = SyncRtSurfaceTransactionApplier(rootView)
+        private val launchContainer = controller.launchContainer
+        @PublishedApi internal val context = launchContainer.context
+        private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer)
         private var animator: ValueAnimator? = null
 
         private var windowCrop = Rect()
@@ -291,11 +296,11 @@
 
         @PublishedApi
         internal fun postTimeout() {
-            rootView.postDelayed(onTimeout, LAUNCH_TIMEOUT)
+            launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT)
         }
 
         private fun removeTimeout() {
-            rootView.removeCallbacks(onTimeout)
+            launchContainer.removeCallbacks(onTimeout)
         }
 
         override fun onAnimationStart(
@@ -369,11 +374,11 @@
             val endWidth = endRight - endLeft
 
             // TODO(b/184121838): Ensure that we are launching on the same screen.
-            val rootViewLocation = rootView.locationOnScreen
+            val rootViewLocation = launchContainer.locationOnScreen
             val isExpandingFullyAbove = endTop <= rootViewLocation[1] &&
-                endBottom >= rootViewLocation[1] + rootView.height &&
+                endBottom >= rootViewLocation[1] + launchContainer.height &&
                 endLeft <= rootViewLocation[0] &&
-                endRight >= rootViewLocation[0] + rootView.width
+                endRight >= rootViewLocation[0] + launchContainer.width
 
             // TODO(b/184121838): We should somehow get the top and bottom radius of the window.
             val endRadius = if (isExpandingFullyAbove) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
new file mode 100644
index 0000000..d4be253
--- /dev/null
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
@@ -0,0 +1,10 @@
+package com.android.systemui.animation
+
+/**
+ * A base class to easily create an implementation of [ActivityLaunchAnimator.Controller] which
+ * delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily
+ * create such a delegated class.
+ */
+open class DelegateLaunchAnimatorController(
+    protected val delegate: ActivityLaunchAnimator.Controller
+) : ActivityLaunchAnimator.Controller by delegate
\ No newline at end of file
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 3da4521..ce9feed 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -14,6 +14,7 @@
 import android.view.GhostView
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewGroupOverlay
 import android.widget.FrameLayout
 import kotlin.math.min
 
@@ -32,9 +33,10 @@
     /** The view that will be ghosted and from which the background will be extracted. */
     private val ghostedView: View
 ) : ActivityLaunchAnimator.Controller {
-    /** The root view to which we will add the ghost view and expanding background. */
-    private val rootView = ghostedView.rootView as ViewGroup
-    private val rootViewOverlay = rootView.overlay
+    /** The container to which we will add the ghost view and expanding background. */
+    override var launchContainer = ghostedView.rootView as ViewGroup
+    private val launchContainerOverlay: ViewGroupOverlay
+        get() = launchContainer.overlay
 
     /** The ghost view that is drawn and animated instead of the ghosted view. */
     private var ghostView: GhostView? = null
@@ -42,7 +44,7 @@
     private val ghostViewMatrix = Matrix()
 
     /**
-     * The expanding background view that will be added to [rootView] (below [ghostView]) and
+     * The expanding background view that will be added to [launchContainer] (below [ghostView]) and
      * animate.
      */
     private var backgroundView: FrameLayout? = null
@@ -96,10 +98,6 @@
         return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
     }
 
-    override fun getRootView(): View {
-        return rootView
-    }
-
     override fun createAnimatorState(): ActivityLaunchAnimator.State {
         val location = ghostedView.locationOnScreen
         return ActivityLaunchAnimator.State(
@@ -113,10 +111,10 @@
     }
 
     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
-        backgroundView = FrameLayout(rootView.context).apply {
+        backgroundView = FrameLayout(launchContainer.context).apply {
             forceHasOverlappingRendering(false)
         }
-        rootViewOverlay.add(backgroundView)
+        launchContainerOverlay.add(backgroundView)
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -127,7 +125,7 @@
 
         // Create a ghost of the view that will be moving and fading out. This allows to fade out
         // the content before fading out the background.
-        ghostView = GhostView.addGhost(ghostedView, rootView).apply {
+        ghostView = GhostView.addGhost(ghostedView, launchContainer).apply {
             setLayerType(View.LAYER_TYPE_HARDWARE, null)
         }
 
@@ -169,7 +167,7 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
-        rootViewOverlay.remove(backgroundView)
+        launchContainerOverlay.remove(backgroundView)
         ghostedView.invalidate()
     }
 
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 085263a..61a8e8e 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -17,7 +17,7 @@
 
 <ripple xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        android:color="@color/notification_ripple_untinted_color">
+        android:color="?android:attr/colorControlHighlight">
     <item>
         <shape>
             <solid android:color="?androidprv:attr/colorSurface" />
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index 1c4c89f..8535426 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -37,7 +37,14 @@
         android:orientation="vertical"
         app:layout_constraintGuide_percent="0.25" />
 
-    <ImageView
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/media_horizontal_center_guideline"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        app:layout_constraintGuide_percent="0.5" />
+
+    <com.android.internal.widget.CachingIconView
         android:id="@+id/recommendation_card_icon"
         android:layout_width="@dimen/qs_aa_media_rec_header_icon_size"
         android:layout_height="@dimen/qs_aa_media_rec_header_icon_size"
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 7142929..5176d96 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -26,6 +26,11 @@
     android:fitsSystemWindows="true">
 
     <FrameLayout
+        android:id="@+id/status_bar_launch_animation_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <FrameLayout
         android:id="@+id/status_bar_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 13c285f..edd8486 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1279,9 +1279,10 @@
     <dimen name="qs_media_disabled_seekbar_vertical_padding">36dp</dimen>
 
     <!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
-    <dimen name="qs_aa_media_rec_header_icon_padding">10dp</dimen>
+    <dimen name="qs_aa_media_rec_header_icon_start_margin">10dp</dimen>
     <dimen name="qs_aa_media_rec_header_icon_size">18dp</dimen>
     <dimen name="qs_aa_media_rec_album_size">72dp</dimen>
+    <dimen name="qs_aa_media_rec_album_vertical_margin">8dp</dimen>
     <dimen name="qq_aa_media_rec_header_text_size">16sp</dimen>
 
     <!-- Window magnification -->
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
index afd800b..31a924c 100644
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
@@ -22,8 +22,8 @@
         android:id="@+id/recommendation_card_icon"
         android:layout_width="@dimen/qs_aa_media_rec_header_icon_size"
         android:layout_height="@dimen/qs_aa_media_rec_header_icon_size"
-        android:layout_marginTop="@dimen/qs_aa_media_rec_header_icon_padding"
-        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -33,7 +33,7 @@
         android:id="@+id/recommendation_card_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
         app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -45,9 +45,12 @@
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
+        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginBottom="@dimen/qs_media_padding"
         app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
         app:layout_constraintEnd_toStartOf="@id/media_cover2"
         app:layout_constraintHorizontal_chainStyle="spread"
+        app:layout_constraintVertical_chainStyle="spread"
         android:visibility="gone" />
 
     <Constraint
@@ -64,6 +67,8 @@
         android:id="@+id/media_cover2"
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
+        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginBottom="@dimen/qs_media_padding"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toEndOf="@id/media_cover1"
@@ -85,6 +90,8 @@
         android:id="@+id/media_cover3"
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
+        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginBottom="@dimen/qs_media_padding"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toEndOf="@id/media_cover2"
@@ -112,7 +119,6 @@
         app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
         app:layout_constraintEnd_toStartOf="@id/media_cover5"
         app:layout_constraintHorizontal_chainStyle="spread"
-        app:layout_constraintHorizontal_bias="1"
         android:visibility="gone" />
 
     <Constraint
@@ -134,7 +140,6 @@
         app:layout_constraintStart_toEndOf="@+id/media_cover4"
         app:layout_constraintEnd_toStartOf="@+id/media_cover6"
         app:layout_constraintHorizontal_chainStyle="spread"
-        app:layout_constraintHorizontal_bias="1"
         android:visibility="gone" />
 
     <Constraint
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
index 04a4877..1411030 100644
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
@@ -23,7 +23,7 @@
         android:layout_width="@dimen/qs_aa_media_rec_header_icon_size"
         android:layout_height="@dimen/qs_aa_media_rec_header_icon_size"
         android:layout_marginTop="@dimen/qs_media_padding"
-        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -33,7 +33,7 @@
         android:id="@+id/recommendation_card_text"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_padding"
+        android:layout_marginStart="@dimen/qs_aa_media_rec_header_icon_start_margin"
         app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
@@ -44,11 +44,14 @@
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
         android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginBottom="@dimen/qs_aa_media_rec_album_vertical_margin"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@+id/media_cover4"
+        app:layout_constraintBottom_toTopOf="@+id/media_horizontal_center_guideline"
         app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
         app:layout_constraintEnd_toStartOf="@id/media_cover2"
         app:layout_constraintHorizontal_chainStyle="spread"
+        app:layout_constraintVertical_chainStyle="spread"
+        app:layout_constraintVertical_bias="0"
         android:visibility="gone" />
 
     <Constraint
@@ -66,11 +69,14 @@
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
         android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginBottom="@dimen/qs_aa_media_rec_album_vertical_margin"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@+id/media_cover5"
+        app:layout_constraintBottom_toTopOf="@+id/media_horizontal_center_guideline"
         app:layout_constraintStart_toEndOf="@id/media_cover1"
         app:layout_constraintEnd_toStartOf="@id/media_cover3"
         app:layout_constraintHorizontal_chainStyle="spread"
+        app:layout_constraintVertical_chainStyle="spread"
+        app:layout_constraintVertical_bias="0"
         android:visibility="gone" />
 
     <Constraint
@@ -88,12 +94,15 @@
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
         android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginBottom="@dimen/qs_aa_media_rec_album_vertical_margin"
         app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toTopOf="@+id/media_cover6"
+        app:layout_constraintBottom_toTopOf="@+id/media_horizontal_center_guideline"
         app:layout_constraintStart_toEndOf="@id/media_cover2"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_chainStyle="spread"
         app:layout_constraintHorizontal_bias="1"
+        app:layout_constraintVertical_chainStyle="spread"
+        app:layout_constraintVertical_bias="0"
         android:visibility="gone" />
 
     <Constraint
@@ -110,14 +119,15 @@
         android:id="@+id/media_cover4"
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
-        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginTop="@dimen/qs_aa_media_rec_album_vertical_margin"
         android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover1"
+        app:layout_constraintTop_toBottomOf="@+id/media_horizontal_center_guideline"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toEndOf="@id/media_vertical_start_guideline"
         app:layout_constraintEnd_toStartOf="@id/media_cover5"
         app:layout_constraintHorizontal_chainStyle="spread"
-        app:layout_constraintHorizontal_bias="1"
+        app:layout_constraintVertical_chainStyle="spread"
+        app:layout_constraintVertical_bias="1"
         android:visibility="gone" />
 
     <Constraint
@@ -134,14 +144,15 @@
         android:id="@+id/media_cover5"
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
-        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginTop="@dimen/qs_aa_media_rec_album_vertical_margin"
         android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constraintTop_toBottomOf="@+id/media_cover2"
+        app:layout_constraintTop_toBottomOf="@+id/media_horizontal_center_guideline"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toEndOf="@+id/media_cover4"
         app:layout_constraintEnd_toStartOf="@+id/media_cover6"
         app:layout_constraintHorizontal_chainStyle="spread"
-        app:layout_constraintHorizontal_bias="1"
+        app:layout_constraintVertical_chainStyle="spread"
+        app:layout_constraintVertical_bias="1"
         android:visibility="gone" />
 
     <Constraint
@@ -158,14 +169,16 @@
         android:id="@+id/media_cover6"
         android:layout_width="@dimen/qs_aa_media_rec_album_size"
         android:layout_height="@dimen/qs_aa_media_rec_album_size"
-        android:layout_marginTop="@dimen/qs_media_padding"
+        android:layout_marginTop="@dimen/qs_aa_media_rec_album_vertical_margin"
         android:layout_marginBottom="@dimen/qs_media_padding"
-        app:layout_constraintTop_toBottomOf="@id/media_cover3"
+        app:layout_constraintTop_toBottomOf="@id/media_horizontal_center_guideline"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toEndOf="@id/media_cover5"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintHorizontal_chainStyle="spread"
         app:layout_constraintHorizontal_bias="1"
+        app:layout_constraintVertical_chainStyle="spread"
+        app:layout_constraintVertical_bias="1"
         android:visibility="gone" />
 
     <Constraint
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index f1431f5..666afed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -51,7 +51,6 @@
 import com.android.internal.policy.IKeyguardService;
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.systemui.SystemUIApplication;
-import com.android.wm.shell.transition.Transitions;
 
 import javax.inject.Inject;
 
@@ -63,29 +62,16 @@
      * Run Keyguard animation as remote animation in System UI instead of local animation in
      * the server process.
      *
-     * 0: Runs all keyguard animation as local animation
-     * 1: Only runs keyguard going away animation as remote animation
-     * 2: Runs all keyguard animation as remote animation
-     *
      * Note: Must be consistent with WindowManagerService.
      */
     private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
             "persist.wm.enable_remote_keyguard_animation";
 
-    private static final int sEnableRemoteKeyguardAnimation =
-            SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
-
     /**
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
      */
-    public static boolean sEnableRemoteKeyguardGoingAwayAnimation =
-            !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 1;
-
-    /**
-     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
-     */
-    public static boolean sEnableRemoteKeyguardOccludeAnimation =
-            !Transitions.ENABLE_SHELL_TRANSITIONS && sEnableRemoteKeyguardAnimation >= 2;
+    static boolean sEnableRemoteKeyguardAnimation =
+            SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
 
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
@@ -97,22 +83,20 @@
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
 
-        RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
-        if (sEnableRemoteKeyguardGoingAwayAnimation) {
+        if (sEnableRemoteKeyguardAnimation) {
+            RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
             final RemoteAnimationAdapter exitAnimationAdapter =
                     new RemoteAnimationAdapter(mExitAnimationRunner, 0, 0);
             definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, exitAnimationAdapter);
             definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
                     exitAnimationAdapter);
-        }
-        if (sEnableRemoteKeyguardOccludeAnimation) {
             final RemoteAnimationAdapter occludeAnimationAdapter =
                     new RemoteAnimationAdapter(mOccludeAnimationRunner, 0, 0);
             definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_OCCLUDE, occludeAnimationAdapter);
             definition.addRemoteAnimation(TRANSIT_OLD_KEYGUARD_UNOCCLUDE, occludeAnimationAdapter);
+            ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
+                    DEFAULT_DISPLAY, definition);
         }
-        ActivityTaskManager.getInstance().registerRemoteAnimationsForDisplay(
-                DEFAULT_DISPLAY, definition);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 85ee0dc..411c328 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -280,7 +280,7 @@
     }
 
     override fun onKeyguardDismissAmountChanged() {
-        if (!KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) {
+        if (!KeyguardService.sEnableRemoteKeyguardAnimation) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b7da7ad..48f9a58 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2100,7 +2100,7 @@
                 playSounds(false);
             }
 
-            if (KeyguardService.sEnableRemoteKeyguardGoingAwayAnimation) {
+            if (KeyguardService.sEnableRemoteKeyguardAnimation) {
                 mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
                 mSurfaceBehindRemoteAnimationRunning = true;
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 8d9a4be..6a2d0af 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.qs.PageIndicator
+import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.Utils
@@ -156,6 +157,13 @@
         }
     }
 
+    var visibleToUser: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+            }
+        }
+
     init {
         mediaFrame = inflateMediaCarousel()
         mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
@@ -203,6 +211,9 @@
             override fun onSmartspaceMediaDataLoaded(key: String, data: SmartspaceTarget) {
                 Log.d(TAG, "My Smartspace media update is here")
                 addSmartspaceMediaRecommendations(key, data)
+                if (visibleToUser) {
+                    logSmartspaceImpression()
+                }
             }
 
             override fun onMediaDataRemoved(key: String) {
@@ -567,6 +578,29 @@
             mediaCarouselScrollHandler.playerWidthPlusPadding = playerWidthPlusPadding
         }
     }
+
+    /**
+     * Log the user impression for media card.
+     */
+    fun logSmartspaceImpression() {
+        MediaPlayerData.players().forEach {
+            // Log every impression of media recommendation card since it will only be shown
+            // for 1 minute after each connection.
+            if (it.recommendationViewHolder?.recommendations?.visibility == View.VISIBLE) {
+                /* ktlint-disable max-line-length */
+                SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
+                        800, // SMARTSPACE_CARD_SEEN
+                        it.getInstanceId(),
+                        SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
+                        it.getSurfaceForSmartspaceLogging(),
+                        /* rank */ 0,
+                        /* cardinality */ 1)
+                /* ktlint-disable max-line-length */
+            }
+
+            // TODO(shijieru): add logging for media control card
+        }
+    }
 }
 
 @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index c713b22..ed16777 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -55,6 +55,7 @@
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
 import com.android.systemui.util.animation.TransitionLayout;
 
+import java.net.URISyntaxException;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -69,6 +70,9 @@
     private static final String TAG = "MediaControlPanel";
     private static final float DISABLED_ALPHA = 0.38f;
     private static final String EXTRAS_MEDIA_SOURCE_PACKAGE_NAME = "package_name";
+    private static final String EXTRAS_SMARTSPACE_INTENT =
+            "com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
+    private static final String KEY_SMARTSPACE_OPEN_IN_FOREGROUND = "KEY_OPEN_IN_FOREGROUND";
     private static final int MEDIA_RECOMMENDATION_ITEMS_PER_ROW = 3;
     private static final int MEDIA_RECOMMENDATION_MAX_NUM = 6;
 
@@ -100,6 +104,8 @@
     private int mBackgroundColor;
     private int mDevicePadding;
     private int mAlbumArtSize;
+    // Instance id for logging purpose.
+    private int mInstanceId;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
 
     /**
@@ -468,6 +474,7 @@
         mRecommendationViewHolder.getCardIcon().setColorFilter(primaryColor);
         mRecommendationViewHolder.getCardText().setTextColor(primaryColor);
 
+        mInstanceId = target.getSmartspaceTargetId().hashCode();
         mRecommendationViewHolder.getRecommendations()
                 .setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
         mBackgroundColor = backgroundColor;
@@ -486,8 +493,10 @@
         ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
         int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
                 MEDIA_RECOMMENDATION_MAX_NUM);
-        for (int i = 0; i < mediaRecommendationNum; i++) {
-            SmartspaceAction recommendation = mediaRecommendationList.get(i);
+        for (int itemIndex = 0, uiComponentIndex = 0;
+                itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
+                itemIndex++) {
+            SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex);
             if (recommendation.getIcon() == null) {
                 Log.w(TAG, "No media cover is provided. Skipping this item...");
                 continue;
@@ -511,31 +520,38 @@
             }
 
             // Set up media source app's logo.
-            ImageView mediaSourceLogoImageView = mediaLogoItems.get(i);
+            ImageView mediaSourceLogoImageView = mediaLogoItems.get(uiComponentIndex);
             mediaSourceLogoImageView.setImageDrawable(icon);
             // TODO(b/186699032): Tint the app logo using the accent color.
             mediaSourceLogoImageView.setColorFilter(backgroundColor, PorterDuff.Mode.XOR);
 
             // Set up media item cover.
-            ImageView mediaCoverImageView = mediaCoverItems.get(i);
+            ImageView mediaCoverImageView = mediaCoverItems.get(uiComponentIndex);
             mediaCoverImageView.setImageIcon(recommendation.getIcon());
 
             // Set up the click listener if applicable.
             setSmartspaceRecItemOnClickListener(
                     mediaCoverImageView,
                     recommendation,
-                    target.getSmartspaceTargetId(),
                     null);
 
-            if (i < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
-                setVisibleAndAlpha(collapsedSet, mediaCoverItemsResIds.get(i), true);
-                setVisibleAndAlpha(collapsedSet, mediaLogoItemsResIds.get(i), true);
+            if (uiComponentIndex < MEDIA_RECOMMENDATION_ITEMS_PER_ROW) {
+                setVisibleAndAlpha(collapsedSet,
+                        mediaCoverItemsResIds.get(uiComponentIndex), true);
+                setVisibleAndAlpha(collapsedSet,
+                        mediaLogoItemsResIds.get(uiComponentIndex), true);
             } else {
-                setVisibleAndAlpha(collapsedSet, mediaCoverItemsResIds.get(i), false);
-                setVisibleAndAlpha(collapsedSet, mediaLogoItemsResIds.get(i), false);
+                setVisibleAndAlpha(collapsedSet,
+                        mediaCoverItemsResIds.get(uiComponentIndex), false);
+                setVisibleAndAlpha(collapsedSet,
+                        mediaLogoItemsResIds.get(uiComponentIndex), false);
             }
-            setVisibleAndAlpha(expandedSet, mediaCoverItemsResIds.get(i), true);
-            setVisibleAndAlpha(expandedSet, mediaLogoItemsResIds.get(i), true);
+            setVisibleAndAlpha(expandedSet,
+                    mediaCoverItemsResIds.get(uiComponentIndex), true);
+            setVisibleAndAlpha(expandedSet,
+                    mediaLogoItemsResIds.get(uiComponentIndex), true);
+
+            uiComponentIndex++;
         }
 
         // Set up long press to show guts setting panel.
@@ -635,7 +651,6 @@
     private void setSmartspaceRecItemOnClickListener(
             @NonNull View view,
             @NonNull SmartspaceAction action,
-            @NonNull String targetId,
             @Nullable View.OnClickListener callback) {
         if (view == null || action == null || action.getIntent() == null) {
             Log.e(TAG, "No tap action can be set up");
@@ -646,24 +661,60 @@
             // When media recommendation card is shown, there could be only one card.
             SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
                     760, // SMARTSPACE_CARD_CLICK
-                    targetId.hashCode(),
+                    mInstanceId,
                     SysUiStatsLog
                             .SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS,
-                    getSurfaceForSmartspaceLogging(mMediaViewController.getCurrentEndLocation()),
-                    /* rank */ 1,
+                    getSurfaceForSmartspaceLogging(),
+                    /* rank */ 0,
                     /* cardinality */ 1);
 
-            mActivityStarter.postStartActivityDismissingKeyguard(
-                    action.getIntent(),
-                    0 /* delay */,
-                    buildLaunchAnimatorController(mRecommendationViewHolder.getRecommendations()));
+            if (shouldSmartspaceRecItemOpenInForeground(action)) {
+                // Request to unlock the device if the activity needs to be opened in foreground.
+                mActivityStarter.postStartActivityDismissingKeyguard(
+                        action.getIntent(),
+                        0 /* delay */,
+                        buildLaunchAnimatorController(
+                                mRecommendationViewHolder.getRecommendations()));
+            } else {
+                // Otherwise, open the activity in background directly.
+                view.getContext().startActivity(action.getIntent());
+            }
+
             if (callback != null) {
                 callback.onClick(v);
             }
         });
     }
 
-    private int getSurfaceForSmartspaceLogging(int currentEndLocation) {
+    /** Returns if the Smartspace action will open the activity in foreground. */
+    private boolean shouldSmartspaceRecItemOpenInForeground(SmartspaceAction action) {
+        if (action == null || action.getIntent() == null
+                || action.getIntent().getExtras() == null) {
+            return false;
+        }
+
+        String intentString = action.getIntent().getExtras().getString(EXTRAS_SMARTSPACE_INTENT);
+        if (intentString == null) {
+            return false;
+        }
+
+        try {
+            Intent wrapperIntent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
+            return wrapperIntent.getBooleanExtra(KEY_SMARTSPACE_OPEN_IN_FOREGROUND, false);
+        } catch (URISyntaxException e) {
+            Log.wtf(TAG, "Failed to create intent from URI: " + intentString);
+            e.printStackTrace();
+        }
+
+        return false;
+    }
+
+    /**
+     * Get the surface given the current end location for MediaViewController
+     * @return surface used for Smartspace logging
+     */
+    protected int getSurfaceForSmartspaceLogging() {
+        int currentEndLocation = mMediaViewController.getCurrentEndLocation();
         if (currentEndLocation == MediaHierarchyManager.LOCATION_QQS
                 || currentEndLocation == MediaHierarchyManager.LOCATION_QS) {
             return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__SHADE;
@@ -672,4 +723,8 @@
         }
         return SysUiStatsLog.SMART_SPACE_CARD_REPORTED__DISPLAY_SURFACE__DEFAULT_SURFACE;
     }
+
+    protected int getInstanceId() {
+        return mInstanceId;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 30bc8c1..a80a410 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
 import javax.inject.Inject
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 
 /**
  * Similarly to isShown but also excludes views that have 0 alpha
@@ -73,7 +74,8 @@
     private val bypassController: KeyguardBypassController,
     private val mediaCarouselController: MediaCarouselController,
     private val notifLockscreenUserManager: NotificationLockscreenUserManager,
-    wakefulnessLifecycle: WakefulnessLifecycle
+    wakefulnessLifecycle: WakefulnessLifecycle,
+    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager
 ) {
     /**
      * The root overlay of the hierarchy. This is where the media notification is attached to
@@ -162,6 +164,26 @@
         }
 
     /**
+     * Is quick setting expanded?
+     */
+    var qsExpanded: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+            }
+            // Pull down shade from lock screen (exclude the case when shade is brought out by
+            // tapping twice on lock screen)
+            if (value && isLockScreenShadeVisibleToUser()) {
+                mediaCarouselController.logSmartspaceImpression()
+            }
+            // Release shade and back to lock screen
+            if (isLockScreenVisibleToUser()) {
+                mediaCarouselController.logSmartspaceImpression()
+            }
+            mediaCarouselController.visibleToUser = isVisibleToUser()
+        }
+
+    /**
      * Is the shade currently collapsing from the expanded qs? If we're on the lockscreen and in qs,
      * we wouldn't want to transition in that case.
      */
@@ -231,6 +253,11 @@
 
             override fun onStateChanged(newState: Int) {
                 updateTargetState()
+                // Enters shade from lock screen
+                if (newState == StatusBarState.SHADE_LOCKED && isLockScreenShadeVisibleToUser()) {
+                    mediaCarouselController.logSmartspaceImpression()
+                }
+                mediaCarouselController.visibleToUser = isVisibleToUser()
             }
 
             override fun onDozeAmountChanged(linear: Float, eased: Float) {
@@ -240,9 +267,27 @@
             override fun onDozingChanged(isDozing: Boolean) {
                 if (!isDozing) {
                     dozeAnimationRunning = false
+                    // Enters lock screen from screen off
+                    if (isLockScreenVisibleToUser()) {
+                        mediaCarouselController.logSmartspaceImpression()
+                    }
                 } else {
                     updateDesiredLocation()
+                    qsExpanded = false
                 }
+                mediaCarouselController.visibleToUser = isVisibleToUser()
+            }
+
+            override fun onExpandedChanged(isExpanded: Boolean) {
+                // Enters shade from home screen
+                if (isHomeScreenShadeVisibleToUser()) {
+                    mediaCarouselController.logSmartspaceImpression()
+                }
+                // Back to lock screen from bouncer
+                if (isLockScreenVisibleToUser()) {
+                    mediaCarouselController.logSmartspaceImpression()
+                }
+                mediaCarouselController.visibleToUser = isVisibleToUser()
             }
         })
 
@@ -622,6 +667,36 @@
         return location
     }
 
+    /**
+     * Returns true when the media card could be visible to the user if existed.
+     */
+    private fun isVisibleToUser(): Boolean {
+        return isLockScreenVisibleToUser() || isLockScreenShadeVisibleToUser() ||
+                isHomeScreenShadeVisibleToUser()
+    }
+
+    private fun isLockScreenVisibleToUser(): Boolean {
+        return !statusBarStateController.isDozing &&
+                !statusBarKeyguardViewManager.isBouncerShowing &&
+                statusBarStateController.state == StatusBarState.KEYGUARD &&
+                notifLockscreenUserManager.shouldShowLockscreenNotifications() &&
+                statusBarStateController.isExpanded &&
+                !qsExpanded
+    }
+
+    private fun isLockScreenShadeVisibleToUser(): Boolean {
+        return !statusBarStateController.isDozing &&
+                !statusBarKeyguardViewManager.isBouncerShowing &&
+                (statusBarStateController.state == StatusBarState.SHADE_LOCKED ||
+                        (statusBarStateController.state == StatusBarState.KEYGUARD && qsExpanded))
+    }
+
+    private fun isHomeScreenShadeVisibleToUser(): Boolean {
+        return !statusBarStateController.isDozing &&
+                statusBarStateController.state == StatusBarState.SHADE &&
+                statusBarStateController.isExpanded
+    }
+
     companion object {
         /**
          * Attached in expanded quick settings
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 4e41d75..56375ad 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -101,7 +101,6 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
-import android.view.inputmethod.InputMethodManager;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -1176,9 +1175,6 @@
         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
         updateAccessibilityServicesState(mAccessibilityManager);
 
-        ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton();
-        imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
-
         updateScreenPinningGestures();
     }
 
@@ -1278,11 +1274,6 @@
         mCommandQueue.toggleRecentApps();
     }
 
-    private void onImeSwitcherClick(View v) {
-        mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
-                true /* showAuxiliarySubtypes */, mDisplayId);
-    };
-
     private boolean onLongPressBackHome(View v) {
         return onLongPressNavigationButtons(v, R.id.back, R.id.home);
     }
@@ -1291,6 +1282,7 @@
         return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps);
     }
 
+
     /**
      * This handles long-press of both back and recents/home. Back is the common button with
      * combination of recents if it is visible or home if recents is invisible.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 66cfae4..3544f60 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -217,9 +217,6 @@
 
     @Override
     public void onNavigationModeChanged(int mode) {
-        if (mNavMode == mode) {
-            return;
-        }
         final int oldMode = mNavMode;
         mNavMode = mode;
         mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
index 4d9175b..7342f91 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
@@ -158,6 +158,7 @@
     }
 
     public void onLikelyDefaultLayoutChange() {
+
         // Reevaluate new layout
         final String newValue = getDefaultLayout();
         if (!Objects.equals(mCurrentLayout, newValue)) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index bdd2735..0ed4d86 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -166,7 +166,6 @@
     private NavigationBarInflaterView mNavigationInflaterView;
     private RecentsOnboarding mRecentsOnboarding;
     private NotificationPanelViewController mPanelView;
-    private RotationContextButton mRotationContextButton;
     private FloatingRotationButton mFloatingRotationButton;
     private RotationButtonController mRotationButtonController;
     private NavigationBarOverlayController mNavBarOverlayController;
@@ -234,6 +233,14 @@
         }
     }
 
+    private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
+                    true /* showAuxiliarySubtypes */, getContext().getDisplayId());
+        }
+    };
+
     private final AccessibilityDelegate mQuickStepAccessibilityDelegate =
             new AccessibilityDelegate() {
                 private AccessibilityAction mToggleOverviewAction;
@@ -304,26 +311,32 @@
         mIsVertical = false;
         mLongClickableAccessibilityButton = false;
         mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
+        boolean isGesturalMode = isGesturalMode(mNavBarMode);
 
         mSysUiFlagContainer = Dependency.get(SysUiState.class);
         // Set up the context group of buttons
         mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
         final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
                 mLightContext, R.drawable.ic_ime_switcher_default);
+        final RotationContextButton rotateSuggestionButton = new RotationContextButton(
+                R.id.rotate_suggestion, mLightContext,
+                R.drawable.ic_sysbar_rotate_button_ccw_start_0);
         final ContextualButton accessibilityButton =
                 new ContextualButton(R.id.accessibility_button, mLightContext,
                         R.drawable.ic_sysbar_accessibility_button);
         mContextualButtonGroup.addButton(imeSwitcherButton);
+        if (!isGesturalMode) {
+            mContextualButtonGroup.addButton(rotateSuggestionButton);
+        }
         mContextualButtonGroup.addButton(accessibilityButton);
 
         mOverviewProxyService = Dependency.get(OverviewProxyService.class);
-        mRotationContextButton = new RotationContextButton(R.id.rotate_suggestion,
-                mLightContext, R.drawable.ic_sysbar_rotate_button_ccw_start_0);
         mFloatingRotationButton = new FloatingRotationButton(context);
         mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
         mRotationButtonController = new RotationButtonController(mLightContext,
-                mLightIconColor, mDarkIconColor);
-        updateRotationButton();
+                mLightIconColor, mDarkIconColor,
+                isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
+                mRotationButtonListener);
 
         mNavBarOverlayController = Dependency.get(NavigationBarOverlayController.class);
         if (mNavBarOverlayController.isNavigationBarOverlayEnabled()) {
@@ -344,6 +357,7 @@
         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
         mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
         mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
+        mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
         mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
         mDeadZone = new DeadZone(this);
 
@@ -541,23 +555,6 @@
         }
     }
 
-    /**
-     * Updates the rotation button based on the current navigation mode.
-     */
-    private void updateRotationButton() {
-        if (isGesturalMode(mNavBarMode)) {
-            mContextualButtonGroup.removeButton(R.id.rotate_suggestion);
-            mButtonDispatchers.remove(R.id.rotate_suggestion);
-            mRotationButtonController.setRotationButton(mFloatingRotationButton,
-                    mRotationButtonListener);
-        } else if (mContextualButtonGroup.getContextButton(R.id.rotate_suggestion) == null) {
-            mContextualButtonGroup.addButton(mRotationContextButton);
-            mButtonDispatchers.put(R.id.rotate_suggestion, mRotationContextButton);
-            mRotationButtonController.setRotationButton(mRotationContextButton,
-                    mRotationButtonListener);
-        }
-    }
-
     public KeyButtonDrawable getBackDrawable() {
         KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
         orientBackButton(drawable);
@@ -911,7 +908,6 @@
         mBarTransitions.onNavigationModeChanged(mNavBarMode);
         mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
         mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
-        updateRotationButton();
 
         if (isGesturalMode(mNavBarMode)) {
             mRegionSamplingHelper.start(mSamplingBounds);
@@ -936,6 +932,7 @@
         mNavigationInflaterView = findViewById(R.id.navigation_inflater);
         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
 
+        getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
         updateOrientationViews();
         reloadNavIcons();
     }
@@ -1030,9 +1027,6 @@
 
     private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace,
             boolean useNearestRegion) {
-        if (button == null) {
-            return;
-        }
         View view = button.getCurrentView();
         if (view == null || !button.isVisible()) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index ddf089b..4bcb019 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -66,10 +66,10 @@
     private static final int NUM_ACCEPTED_ROTATION_SUGGESTIONS_FOR_INTRODUCTION = 3;
 
     private final Context mContext;
+    private final RotationButton mRotationButton;
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
     private final UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
     private final ViewRippler mViewRippler = new ViewRippler();
-    private RotationButton mRotationButton;
 
     private int mLastRotationSuggestion;
     private boolean mPendingRotationSuggestion;
@@ -125,21 +125,20 @@
     }
 
     RotationButtonController(Context context, @ColorInt int lightIconColor,
-            @ColorInt int darkIconColor) {
+            @ColorInt int darkIconColor, RotationButton rotationButton,
+            Consumer<Boolean> visibilityChangedCallback) {
         mContext = context;
         mLightIconColor = lightIconColor;
         mDarkIconColor = darkIconColor;
+        mRotationButton = rotationButton;
+        mRotationButton.setRotationButtonController(this);
 
         mIsNavigationBarShowing = true;
         mRotationLockController = Dependency.get(RotationLockController.class);
         mAccessibilityManagerWrapper = Dependency.get(AccessibilityManagerWrapper.class);
-        mTaskStackListener = new TaskStackListenerImpl();
-    }
 
-    void setRotationButton(RotationButton rotationButton,
-            Consumer<Boolean> visibilityChangedCallback) {
-        mRotationButton = rotationButton;
-        mRotationButton.setRotationButtonController(this);
+        // Register the task stack listener
+        mTaskStackListener = new TaskStackListenerImpl();
         mRotationButton.setOnClickListener(this::onRotateSuggestionClick);
         mRotationButton.setOnHoverListener(this::onRotateSuggestionHover);
         mRotationButton.setVisibilityChangedCallback(visibilityChangedCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
index 2ace303..50b638b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/ContextualButtonGroup.java
@@ -41,23 +41,10 @@
      * @param button the button added to the group
      */
     public void addButton(@NonNull ContextualButton button) {
-        // By default buttons in the context group are not visible until
-        // {@link #setButtonVisibility()) is called to show one of the buttons
-        button.setVisibility(View.INVISIBLE);
         button.attachToGroup(this);
         mButtonData.add(new ButtonData(button));
     }
 
-    /**
-     * Removes a contextual button from the group.
-     */
-    public void removeButton(@IdRes int buttonResId) {
-        int index = getContextButtonIndex(buttonResId);
-        if (index != INVALID_INDEX) {
-            mButtonData.remove(index);
-        }
-    }
-
     public ContextualButton getContextButton(@IdRes int buttonResId) {
         int index = getContextButtonIndex(buttonResId);
         if (index != INVALID_INDEX) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index f078ccd..1ec785d47 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -213,7 +213,9 @@
                 // TODO move this logic to message queue
                 mStatusBarOptionalLazy.ifPresent(statusBarLazy -> {
                     StatusBar statusBar = statusBarLazy.get();
-                    statusBar.getPanelController().startExpandLatencyTracking();
+                    if (event.getActionMasked() == ACTION_DOWN) {
+                        statusBar.getPanelController().startExpandLatencyTracking();
+                    }
                     mHandler.post(()-> {
                         int action = event.getActionMasked();
                         if (action == ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 6b68fc6..a072de8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -924,7 +924,8 @@
             // check of whether non-strong biometric is allowed
             return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
                     && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
-                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
+                    || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
+                    || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED);
         }
 
         private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 5437ce6..7f31fdd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -18,6 +18,7 @@
 
 import static com.android.systemui.statusbar.RemoteInputController.processForRemoteInput;
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -34,7 +35,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
 import com.android.systemui.statusbar.phone.NotificationListenerWithPlugins;
-import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -46,7 +46,6 @@
 @SuppressLint("OverrideAbstract")
 public class NotificationListener extends NotificationListenerWithPlugins {
     private static final String TAG = "NotificationListener";
-    private static final boolean DEBUG = StatusBar.DEBUG;
 
     private final Context mContext;
     private final NotificationManager mNotificationManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 8194220..c4e2279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -1,6 +1,6 @@
 package com.android.systemui.statusbar.notification
 
-import android.view.View
+import android.view.ViewGroup
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -45,7 +45,11 @@
 ) : ActivityLaunchAnimator.Controller {
     private val notificationKey = notification.entry.sbn.key
 
-    override fun getRootView(): View = notification.rootView
+    override var launchContainer: ViewGroup
+        get() = notification.rootView as ViewGroup
+        set(ignored) {
+            // Do nothing. Notifications are always animated inside their rootView.
+        }
 
     override fun createAnimatorState(): ActivityLaunchAnimator.State {
         // If the notification panel is collapsed, the clip may be larger than the height.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index d95c265..d6356de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -16,9 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.legacy;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.Log;
@@ -33,7 +31,6 @@
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
-import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.wm.shell.bubbles.Bubbles;
@@ -42,12 +39,10 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.TreeSet;
 
 import javax.inject.Inject;
 
@@ -63,21 +58,13 @@
 public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener, StateListener,
         GroupMembershipManager, GroupExpansionManager, Dumpable {
 
-    private static final String TAG = "NotifGroupManager";
-    private static final boolean DEBUG = StatusBar.DEBUG;
-    private static final boolean SPEW = StatusBar.SPEW;
-    /**
-     * The maximum amount of time (in ms) between the posting of notifications that can be
-     * considered part of the same update batch.
-     */
-    private static final long POST_BATCH_MAX_AGE = 5000;
+    private static final String TAG = "NotificationGroupManager";
     private final HashMap<String, NotificationGroup> mGroupMap = new HashMap<>();
     private final ArraySet<OnGroupExpansionChangeListener> mExpansionChangeListeners =
             new ArraySet<>();
     private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
     private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
     private final Optional<Bubbles> mBubblesOptional;
-    private final EventBuffer mEventBuffer = new EventBuffer();
     private int mBarState = -1;
     private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
     private HeadsUpManager mHeadsUpManager;
@@ -147,14 +134,8 @@
      * When we want to remove an entry from being tracked for grouping
      */
     public void onEntryRemoved(NotificationEntry removed) {
-        if (SPEW) {
-            Log.d(TAG, "onEntryRemoved: entry=" + removed);
-        }
         onEntryRemovedInternal(removed, removed.getSbn());
-        StatusBarNotification oldSbn = mIsolatedEntries.remove(removed.getKey());
-        if (oldSbn != null) {
-            updateSuppression(mGroupMap.get(oldSbn.getGroupKey()));
-        }
+        mIsolatedEntries.remove(removed.getKey());
     }
 
     /**
@@ -181,9 +162,6 @@
             // the close future. See b/23676310 for reference.
             return;
         }
-        if (SPEW) {
-            Log.d(TAG, "onEntryRemovedInternal: entry=" + removed + " group=" + group.groupKey);
-        }
         if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
             group.children.remove(removed.getKey());
         } else {
@@ -204,9 +182,6 @@
      * Notify the group manager that a new entry was added
      */
     public void onEntryAdded(final NotificationEntry added) {
-        if (SPEW) {
-            Log.d(TAG, "onEntryAdded: entry=" + added);
-        }
         updateIsolation(added);
         onEntryAddedInternal(added);
     }
@@ -220,16 +195,13 @@
         String groupKey = getGroupKey(sbn);
         NotificationGroup group = mGroupMap.get(groupKey);
         if (group == null) {
-            group = new NotificationGroup(groupKey);
+            group = new NotificationGroup();
             mGroupMap.put(groupKey, group);
 
             for (OnGroupChangeListener listener : mGroupChangeListeners) {
                 listener.onGroupCreated(group, groupKey);
             }
         }
-        if (SPEW) {
-            Log.d(TAG, "onEntryAddedInternal: entry=" + added + " group=" + group.groupKey);
-        }
         if (isGroupChild) {
             NotificationEntry existing = group.children.get(added.getKey());
             if (existing != null && existing != added) {
@@ -241,11 +213,9 @@
                         + " added removed" + added.isRowRemoved(), new Throwable());
             }
             group.children.put(added.getKey(), added);
-            addToPostBatchHistory(group, added);
             updateSuppression(group);
         } else {
             group.summary = added;
-            addToPostBatchHistory(group, added);
             group.expanded = added.areChildrenExpanded();
             updateSuppression(group);
             if (!group.children.isEmpty()) {
@@ -261,27 +231,6 @@
         }
     }
 
-    private void addToPostBatchHistory(NotificationGroup group, @Nullable NotificationEntry entry) {
-        if (entry == null) {
-            return;
-        }
-        boolean didAdd = group.postBatchHistory.add(new PostRecord(entry));
-        if (didAdd) {
-            trimPostBatchHistory(group.postBatchHistory);
-        }
-    }
-
-    /** remove all history that's too old to be in the batch. */
-    private void trimPostBatchHistory(@NonNull TreeSet<PostRecord> postBatchHistory) {
-        if (postBatchHistory.size() <= 1) {
-            return;
-        }
-        long batchStartTime = postBatchHistory.last().postTime - POST_BATCH_MAX_AGE;
-        while (!postBatchHistory.isEmpty() && postBatchHistory.first().postTime < batchStartTime) {
-            postBatchHistory.pollFirst();
-        }
-    }
-
     private void onEntryBecomingChild(NotificationEntry entry) {
         updateIsolation(entry);
     }
@@ -290,9 +239,6 @@
         if (group == null) {
             return;
         }
-        NotificationEntry prevAlertOverride = group.alertOverride;
-        group.alertOverride = getPriorityConversationAlertOverride(group);
-
         int childCount = 0;
         boolean hasBubbles = false;
         for (NotificationEntry entry : group.children.values()) {
@@ -309,148 +255,18 @@
         group.suppressed = group.summary != null && !group.expanded
                 && (childCount == 1
                 || (childCount == 0
-                && group.summary.getSbn().getNotification().isGroupSummary()
-                && (hasIsolatedChildren(group) || hasBubbles)));
-
-        boolean alertOverrideChanged = prevAlertOverride != group.alertOverride;
-        boolean suppressionChanged = prevSuppressed != group.suppressed;
-        if (alertOverrideChanged || suppressionChanged) {
-            if (DEBUG && alertOverrideChanged) {
-                Log.d(TAG, group + " alertOverride was=" + prevAlertOverride + " now="
-                        + group.alertOverride);
-            }
-            if (DEBUG && suppressionChanged) {
-                Log.d(TAG, group + " suppressed changed to " + group.suppressed);
-            }
-            if (!mIsUpdatingUnchangedGroup) {
-                if (alertOverrideChanged) {
-                    mEventBuffer.notifyAlertOverrideChanged(group, prevAlertOverride);
-                }
-                if (suppressionChanged) {
-                    for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                        listener.onGroupSuppressionChanged(group, group.suppressed);
-                    }
-                }
-                mEventBuffer.notifyGroupsChanged();
-            } else {
-                if (DEBUG) {
-                    Log.d(TAG, group + " did not notify listeners of above change(s)");
+                        && group.summary.getSbn().getNotification().isGroupSummary()
+                        && (hasIsolatedChildren(group) || hasBubbles)));
+        if (prevSuppressed != group.suppressed) {
+            for (OnGroupChangeListener listener : mGroupChangeListeners) {
+                if (!mIsUpdatingUnchangedGroup) {
+                    listener.onGroupSuppressionChanged(group, group.suppressed);
+                    listener.onGroupsChanged();
                 }
             }
         }
     }
 
-    /**
-     * Finds the isolated logical child of this group which is should be alerted instead.
-     *
-     * Notifications from priority conversations are isolated from their groups to make them more
-     * prominent, however apps may post these with a GroupAlertBehavior that has the group receiving
-     * the alert.  This would lead to the group alerting even though the conversation that was
-     * updated was not actually a part of that group.  This method finds the best priority
-     * conversation in this situation, if there is one, so they can be set as the alertOverride of
-     * the group.
-     *
-     * @param group the group to check
-     * @return the entry which should receive the alert instead of the group, if any.
-     */
-    @Nullable
-    private NotificationEntry getPriorityConversationAlertOverride(NotificationGroup group) {
-        // GOAL: if there is a priority child which wouldn't alert based on its groupAlertBehavior,
-        // but which should be alerting (because priority conversations are isolated), find it.
-        if (group == null || group.summary == null) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: null group or summary");
-            }
-            return null;
-        }
-        if (isIsolated(group.summary.getKey())) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: isolated group");
-            }
-            return null;
-        }
-
-        // Precondiions:
-        // * Only necessary when all notifications in the group use GROUP_ALERT_SUMMARY
-        // * Only necessary when at least one notification in the group is on a priority channel
-        if (group.summary.getSbn().getNotification().getGroupAlertBehavior()
-                != Notification.GROUP_ALERT_SUMMARY) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: summary != GROUP_ALERT_SUMMARY");
-            }
-            return null;
-        }
-
-        // Get the important children first, copy the keys for the final importance check,
-        // then add the non-isolated children to the map for unified lookup.
-        HashMap<String, NotificationEntry> children = getImportantConversations(group);
-        if (children == null || children.isEmpty()) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: no important conversations");
-            }
-            return null;
-        }
-        HashSet<String> importantChildKeys = new HashSet<>(children.keySet());
-        children.putAll(group.children);
-
-        // Ensure all children have GROUP_ALERT_SUMMARY
-        for (NotificationEntry child : children.values()) {
-            if (child.getSbn().getNotification().getGroupAlertBehavior()
-                    != Notification.GROUP_ALERT_SUMMARY) {
-                if (SPEW) {
-                    Log.d(TAG, "getPriorityConversationAlertOverride: "
-                            + "child != GROUP_ALERT_SUMMARY");
-                }
-                return null;
-            }
-        }
-
-        // Create a merged post history from all the children
-        TreeSet<PostRecord> combinedHistory = new TreeSet<>(group.postBatchHistory);
-        for (String importantChildKey : importantChildKeys) {
-            NotificationGroup importantChildGroup = mGroupMap.get(importantChildKey);
-            combinedHistory.addAll(importantChildGroup.postBatchHistory);
-        }
-        trimPostBatchHistory(combinedHistory);
-
-        // This is a streamlined implementation of the following idea:
-        // * From the subset of notifications in the latest 'batch' of updates.  A batch is:
-        //   * Notifs posted less than POST_BATCH_MAX_AGE before the most recently posted.
-        //   * Only including notifs newer than the second-to-last post of any notification.
-        // * Find the newest child in the batch -- the with the largest 'when' value.
-        // * If the newest child is a priority conversation, set that as the override.
-        HashSet<String> batchKeys = new HashSet<>();
-        long newestChildWhen = -1;
-        NotificationEntry newestChild = null;
-        // Iterate backwards through the post history, tracking the child with the smallest sort key
-        for (PostRecord record : combinedHistory.descendingSet()) {
-            if (batchKeys.contains(record.key)) {
-                // Once you see a notification again, the batch has ended
-                break;
-            }
-            batchKeys.add(record.key);
-            NotificationEntry child = children.get(record.key);
-            if (child != null) {
-                long childWhen = child.getSbn().getNotification().when;
-                if (newestChild == null || childWhen > newestChildWhen) {
-                    newestChildWhen = childWhen;
-                    newestChild = child;
-                }
-            }
-        }
-        if (newestChild != null && importantChildKeys.contains(newestChild.getKey())) {
-            if (SPEW) {
-                Log.d(TAG, "getPriorityConversationAlertOverride: result=" + newestChild);
-            }
-            return newestChild;
-        }
-        if (SPEW) {
-            Log.d(TAG, "getPriorityConversationAlertOverride: result=null, newestChild="
-                    + newestChild);
-        }
-        return null;
-    }
-
     private boolean hasIsolatedChildren(NotificationGroup group) {
         return getNumberOfIsolatedChildren(group.summary.getSbn().getGroupKey()) != 0;
     }
@@ -465,33 +281,12 @@
         return count;
     }
 
-    @Nullable
-    private HashMap<String, NotificationEntry> getImportantConversations(NotificationGroup group) {
-        String groupKey = group.summary.getSbn().getGroupKey();
-        HashMap<String, NotificationEntry> result = null;
-        for (StatusBarNotification sbn : mIsolatedEntries.values()) {
-            if (sbn.getGroupKey().equals(groupKey)) {
-                NotificationEntry entry = mGroupMap.get(sbn.getKey()).summary;
-                if (isImportantConversation(entry)) {
-                    if (result == null) {
-                        result = new HashMap<>();
-                    }
-                    result.put(sbn.getKey(), entry);
-                }
-            }
-        }
-        return result;
-    }
-
     /**
      * Update an entry's group information
      * @param entry notification entry to update
      * @param oldNotification previous notification info before this update
      */
     public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
-        if (SPEW) {
-            Log.d(TAG, "onEntryUpdated: entry=" + entry);
-        }
         onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
                 oldNotification.getNotification().isGroupSummary());
     }
@@ -530,17 +325,7 @@
      * Whether the given notification is the summary of a group that is being suppressed
      */
     public boolean isSummaryOfSuppressedGroup(StatusBarNotification sbn) {
-        return sbn.getNotification().isGroupSummary() && isGroupSuppressed(getGroupKey(sbn));
-    }
-
-    /**
-     * If the given notification is a summary, get the group for it.
-     */
-    public NotificationGroup getGroupForSummary(StatusBarNotification sbn) {
-        if (sbn.getNotification().isGroupSummary()) {
-            return mGroupMap.get(getGroupKey(sbn));
-        }
-        return null;
+        return isGroupSuppressed(getGroupKey(sbn)) && sbn.getNotification().isGroupSummary();
     }
 
     private boolean isOnlyChild(StatusBarNotification sbn) {
@@ -760,7 +545,9 @@
         if (!sbn.isGroup() || sbn.getNotification().isGroupSummary()) {
             return false;
         }
-        if (isImportantConversation(entry)) {
+        int peopleNotificationType =
+                mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
+        if (peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON) {
             return true;
         }
         if (mHeadsUpManager != null && !mHeadsUpManager.isAlerting(entry.getKey())) {
@@ -773,25 +560,18 @@
                     || isGroupNotFullyVisible(notificationGroup));
     }
 
-    private boolean isImportantConversation(NotificationEntry entry) {
-        int peopleNotificationType =
-                mPeopleNotificationIdentifier.get().getPeopleNotificationType(entry);
-        return peopleNotificationType == PeopleNotificationIdentifier.TYPE_IMPORTANT_PERSON;
-    }
-
     /**
      * Isolate a notification from its group so that it visually shows as its own group.
      *
      * @param entry the notification to isolate
      */
     private void isolateNotification(NotificationEntry entry) {
-        if (SPEW) {
-            Log.d(TAG, "isolateNotification: entry=" + entry);
-        }
+        StatusBarNotification sbn = entry.getSbn();
+
         // We will be isolated now, so lets update the groups
         onEntryRemovedInternal(entry, entry.getSbn());
 
-        mIsolatedEntries.put(entry.getKey(), entry.getSbn());
+        mIsolatedEntries.put(sbn.getKey(), sbn);
 
         onEntryAddedInternal(entry);
         // We also need to update the suppression of the old group, because this call comes
@@ -808,14 +588,6 @@
      * Update the isolation of an entry, splitting it from the group.
      */
     public void updateIsolation(NotificationEntry entry) {
-        // We need to buffer a few events because we do isolation changes in 3 steps:
-        // removeInternal, update mIsolatedEntries, addInternal.  This means that often the
-        // alertOverride will update on the removal, however processing the event in that case can
-        // cause problems because the mIsolatedEntries map is not in its final state, so the event
-        // listener may be unable to correctly determine the true state of the group.  By delaying
-        // the alertOverride change until after the add phase, we can ensure that listeners only
-        // have to handle a consistent state.
-        mEventBuffer.startBuffering();
         boolean isIsolated = isIsolated(entry.getSbn().getKey());
         if (shouldIsolate(entry)) {
             if (!isIsolated) {
@@ -824,7 +596,6 @@
         } else if (isIsolated) {
             stopIsolatingNotification(entry);
         }
-        mEventBuffer.flushAndStopBuffering();
     }
 
     /**
@@ -833,15 +604,15 @@
      * @param entry the notification to un-isolate
      */
     private void stopIsolatingNotification(NotificationEntry entry) {
-        if (SPEW) {
-            Log.d(TAG, "stopIsolatingNotification: entry=" + entry);
-        }
-        // not isolated anymore, we need to update the groups
-        onEntryRemovedInternal(entry, entry.getSbn());
-        mIsolatedEntries.remove(entry.getKey());
-        onEntryAddedInternal(entry);
-        for (OnGroupChangeListener listener : mGroupChangeListeners) {
-            listener.onGroupsChanged();
+        StatusBarNotification sbn = entry.getSbn();
+        if (isIsolated(sbn.getKey())) {
+            // not isolated anymore, we need to update the groups
+            onEntryRemovedInternal(entry, entry.getSbn());
+            mIsolatedEntries.remove(sbn.getKey());
+            onEntryAddedInternal(entry);
+            for (OnGroupChangeListener listener : mGroupChangeListeners) {
+                listener.onGroupsChanged();
+            }
         }
     }
 
@@ -877,154 +648,33 @@
     }
 
     /**
-     * A record of a notification being posted, containing the time of the post and the key of the
-     * notification entry.  These are stored in a TreeSet by the NotificationGroup and used to
-     * calculate a batch of notifications.
-     */
-    public static class PostRecord implements Comparable<PostRecord> {
-        public final long postTime;
-        public final String key;
-
-        /** constructs a record containing the post time and key from the notification entry */
-        public PostRecord(@NonNull NotificationEntry entry) {
-            this.postTime = entry.getSbn().getPostTime();
-            this.key = entry.getKey();
-        }
-
-        @Override
-        public int compareTo(PostRecord o) {
-            int postTimeComparison = Long.compare(this.postTime, o.postTime);
-            return postTimeComparison == 0
-                    ? String.CASE_INSENSITIVE_ORDER.compare(this.key, o.key)
-                    : postTimeComparison;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            PostRecord that = (PostRecord) o;
-            return postTime == that.postTime && key.equals(that.key);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(postTime, key);
-        }
-    }
-
-    /**
      * Represents a notification group in the notification shade.
      */
     public static class NotificationGroup {
-        public final String groupKey;
         public final HashMap<String, NotificationEntry> children = new HashMap<>();
-        public final TreeSet<PostRecord> postBatchHistory = new TreeSet<>();
         public NotificationEntry summary;
         public boolean expanded;
         /**
          * Is this notification group suppressed, i.e its summary is hidden
          */
         public boolean suppressed;
-        /**
-         * The child (which is isolated from this group) to which the alert should be transferred,
-         * due to priority conversations.
-         */
-        public NotificationEntry alertOverride;
-
-        NotificationGroup(String groupKey) {
-            this.groupKey = groupKey;
-        }
 
         @Override
         public String toString() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("    groupKey: ").append(groupKey);
-            sb.append("\n    summary:");
-            appendEntry(sb, summary);
-            sb.append("\n    children size: ").append(children.size());
+            String result = "    summary:\n      "
+                    + (summary != null ? summary.getSbn() : "null")
+                    + (summary != null && summary.getDebugThrowable() != null
+                            ? Log.getStackTraceString(summary.getDebugThrowable())
+                            : "");
+            result += "\n    children size: " + children.size();
             for (NotificationEntry child : children.values()) {
-                appendEntry(sb, child);
+                result += "\n      " + child.getSbn()
+                        + (child.getDebugThrowable() != null
+                            ? Log.getStackTraceString(child.getDebugThrowable())
+                            : "");
             }
-            sb.append("\n    alertOverride:");
-            appendEntry(sb, alertOverride);
-            sb.append("\n    summary suppressed: ").append(suppressed);
-            return sb.toString();
-        }
-
-        private void appendEntry(StringBuilder sb, NotificationEntry entry) {
-            sb.append("\n      ").append(entry != null ? entry.getSbn() : "null");
-            if (entry != null && entry.getDebugThrowable() != null) {
-                sb.append(Log.getStackTraceString(entry.getDebugThrowable()));
-            }
-        }
-    }
-
-    /**
-     * This class is a toggleable buffer for a subset of events of {@link OnGroupChangeListener}.
-     * When buffering, instead of notifying the listeners it will set internal state that will allow
-     * it to notify listeners of those events later
-     */
-    private class EventBuffer {
-        private final HashMap<String, NotificationEntry> mOldAlertOverrideByGroup = new HashMap<>();
-        private boolean mIsBuffering = false;
-        private boolean mDidGroupsChange = false;
-
-        void notifyAlertOverrideChanged(NotificationGroup group,
-                NotificationEntry oldAlertOverride) {
-            if (mIsBuffering) {
-                // The value in this map is the override before the event.  If there is an entry
-                // already in the map, then we are effectively coalescing two events, which means
-                // we need to preserve the original initial value.
-                mOldAlertOverrideByGroup.putIfAbsent(group.groupKey, oldAlertOverride);
-            } else {
-                for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                    listener.onGroupAlertOverrideChanged(group, oldAlertOverride,
-                            group.alertOverride);
-                }
-            }
-        }
-
-        void notifyGroupsChanged() {
-            if (mIsBuffering) {
-                mDidGroupsChange = true;
-            } else {
-                for (OnGroupChangeListener listener : mGroupChangeListeners) {
-                    listener.onGroupsChanged();
-                }
-            }
-        }
-
-        void startBuffering() {
-            mIsBuffering = true;
-        }
-
-        void flushAndStopBuffering() {
-            // stop buffering so that we can call our own helpers
-            mIsBuffering = false;
-            // alert all group alert override changes for groups that were not removed
-            for (Map.Entry<String, NotificationEntry> entry : mOldAlertOverrideByGroup.entrySet()) {
-                NotificationGroup group = mGroupMap.get(entry.getKey());
-                if (group == null) {
-                    // The group can be null if this alertOverride changed before the group was
-                    // permanently removed, meaning that there's no guarantee that listeners will
-                    // that field clear.
-                    continue;
-                }
-                NotificationEntry oldAlertOverride = entry.getValue();
-                if (group.alertOverride == oldAlertOverride) {
-                    // If the final alertOverride equals the initial, it means we coalesced two
-                    // events which undid the change, so we can drop it entirely.
-                    continue;
-                }
-                notifyAlertOverrideChanged(group, oldAlertOverride);
-            }
-            mOldAlertOverrideByGroup.clear();
-            // alert that groups changed
-            if (mDidGroupsChange) {
-                notifyGroupsChanged();
-                mDidGroupsChange = false;
-            }
+            result += "\n    summary suppressed: " + suppressed;
+            return result;
         }
     }
 
@@ -1064,18 +714,6 @@
                 boolean suppressed) {}
 
         /**
-         * The alert override of a group has changed.
-         *
-         * @param group the group that has changed
-         * @param oldAlertOverride the previous notification to which the group's alerts were sent
-         * @param newAlertOverride the notification to which the group's alerts should now be sent
-         */
-        default void onGroupAlertOverrideChanged(
-                NotificationGroup group,
-                @Nullable NotificationEntry oldAlertOverride,
-                @Nullable NotificationEntry newAlertOverride) {}
-
-        /**
          * A group of children just received a summary notification and should therefore become
          * children of it.
          *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 7d586ba..c1a5f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -400,10 +400,14 @@
         }
 
         if (view instanceof FooterView) {
-            viewState.yTranslation = Math.min(viewState.yTranslation,
-                    ambientState.getStackHeight());
-            // Hide footer if shelf is showing
-            viewState.hidden = algorithmState.firstViewInShelf != null;
+            final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
+
+            final float footerEnd = viewState.yTranslation + view.getIntrinsicHeight();
+            final boolean noSpaceForFooter = footerEnd > ambientState.getStackHeight();
+
+            viewState.hidden = isShelfShowing
+                    || (!ambientState.isExpansionChanging() && noSpaceForFooter);
+
         } else if (view != ambientState.getTrackedHeadsUpRow()) {
             if (ambientState.isExpansionChanging()) {
                 // Show all views. Views below the shelf will later be clipped (essentially hidden)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 9787a944..3181f52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -22,12 +22,12 @@
 import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -41,21 +41,17 @@
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 
 import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
 
 /**
  * A helper class dealing with the alert interactions between {@link NotificationGroupManagerLegacy}
  * and {@link HeadsUpManager}. In particular, this class deals with keeping
- * the correct notification in a group alerting based off the group suppression and alertOverride.
+ * the correct notification in a group alerting based off the group suppression.
  */
 public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedListener,
         StateListener {
 
     private static final long ALERT_TRANSFER_TIMEOUT = 300;
-    private static final String TAG = "NotifGroupAlertTransfer";
-    private static final boolean DEBUG = StatusBar.DEBUG;
-    private static final boolean SPEW = StatusBar.SPEW;
 
     /**
      * The list of entries containing group alert metadata for each group. Keyed by group key.
@@ -146,98 +142,41 @@
 
         @Override
         public void onGroupSuppressionChanged(NotificationGroup group, boolean suppressed) {
-            if (DEBUG) {
-                Log.d(TAG, "!! onGroupSuppressionChanged: group.summary=" + group.summary
-                        + " suppressed=" + suppressed);
+            if (suppressed) {
+                if (mHeadsUpManager.isAlerting(group.summary.getKey())) {
+                    handleSuppressedSummaryAlerted(group.summary, mHeadsUpManager);
+                }
+            } else {
+                // Group summary can be null if we are no longer suppressed because the summary was
+                // removed. In that case, we don't need to alert the summary.
+                if (group.summary == null) {
+                    return;
+                }
+                GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
+                        group.summary.getSbn()));
+                // Group is no longer suppressed. We should check if we need to transfer the alert
+                // back to the summary now that it's no longer suppressed.
+                if (groupAlertEntry.mAlertSummaryOnNextAddition) {
+                    if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
+                        alertNotificationWhenPossible(group.summary, mHeadsUpManager);
+                    }
+                    groupAlertEntry.mAlertSummaryOnNextAddition = false;
+                } else {
+                    checkShouldTransferBack(groupAlertEntry);
+                }
             }
-            NotificationEntry oldAlertOverride = group.alertOverride;
-            onGroupChanged(group, oldAlertOverride);
-        }
-
-        @Override
-        public void onGroupAlertOverrideChanged(NotificationGroup group,
-                @Nullable NotificationEntry oldAlertOverride,
-                @Nullable NotificationEntry newAlertOverride) {
-            if (DEBUG) {
-                Log.d(TAG, "!! onGroupAlertOverrideChanged: group.summary=" + group.summary
-                        + " oldAlertOverride=" + oldAlertOverride
-                        + " newAlertOverride=" + newAlertOverride);
-            }
-            onGroupChanged(group, oldAlertOverride);
         }
     };
 
-    /**
-     * Called when either the suppressed or alertOverride fields of the group changed
-     *
-     * @param group the group which changed
-     * @param oldAlertOverride the previous value of group.alertOverride
-     */
-    private void onGroupChanged(NotificationGroup group,
-            NotificationEntry oldAlertOverride) {
-        // Group summary can be null if we are no longer suppressed because the summary was
-        // removed. In that case, we don't need to alert the summary.
-        if (group.summary == null) {
-            if (DEBUG) {
-                Log.d(TAG, "onGroupChanged: summary is null");
-            }
-            return;
-        }
-        if (group.suppressed || group.alertOverride != null) {
-            checkForForwardAlertTransfer(group.summary, oldAlertOverride);
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "onGroupChanged: maybe transfer back");
-            }
-            GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(mGroupManager.getGroupKey(
-                    group.summary.getSbn()));
-            // Group is no longer suppressed or overridden.
-            // We should check if we need to transfer the alert back to the summary.
-            if (groupAlertEntry.mAlertSummaryOnNextAddition) {
-                if (!mHeadsUpManager.isAlerting(group.summary.getKey())) {
-                    alertNotificationWhenPossible(group.summary);
-                }
-                groupAlertEntry.mAlertSummaryOnNextAddition = false;
-            } else {
-                checkShouldTransferBack(groupAlertEntry);
-            }
-        }
-    }
-
     @Override
     public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-        if (DEBUG) {
-            Log.d(TAG, "!! onHeadsUpStateChanged: entry=" + entry + " isHeadsUp=" + isHeadsUp);
-        }
-        if (isHeadsUp && entry.getSbn().getNotification().isGroupSummary()) {
-            // a group summary is alerting; trigger the forward transfer checks
-            checkForForwardAlertTransfer(entry, /* oldAlertOverride */ null);
-        }
+        onAlertStateChanged(entry, isHeadsUp, mHeadsUpManager);
     }
 
-    /**
-     * Handles changes in a group's suppression or alertOverride, but where at least one of those
-     * conditions is still true (either the group is suppressed, the group has an alertOverride,
-     * or both).  The method determined which kind of child needs to receive the alert, finds the
-     * entry currently alerting, and makes the transfer.
-     *
-     * Internally, this is handled with two main cases: the override needs the alert, or there is
-     * no override but the summary is suppressed (so an isolated child needs the alert).
-     *
-     * @param summary the notification entry of the summary of the logical group.
-     * @param oldAlertOverride the former value of group.alertOverride, before whatever event
-     *                         required us to check for for a transfer condition.
-     */
-    private void checkForForwardAlertTransfer(NotificationEntry summary,
-            NotificationEntry oldAlertOverride) {
-        if (DEBUG) {
-            Log.d(TAG, "checkForForwardAlertTransfer: enter");
-        }
-        NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
-        if (group != null && group.alertOverride != null) {
-            handleOverriddenSummaryAlerted(summary);
-        } else if (mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())) {
-            handleSuppressedSummaryAlerted(summary, oldAlertOverride);
+    private void onAlertStateChanged(NotificationEntry entry, boolean isAlerting,
+            AlertingNotificationManager alertManager) {
+        if (isAlerting && mGroupManager.isSummaryOfSuppressedGroup(entry.getSbn())) {
+            handleSuppressedSummaryAlerted(entry, alertManager);
         }
     }
 
@@ -247,16 +186,9 @@
         // see as early as we can if we need to abort a transfer.
         @Override
         public void onPendingEntryAdded(NotificationEntry entry) {
-            if (DEBUG) {
-                Log.d(TAG, "!! onPendingEntryAdded: entry=" + entry);
-            }
             String groupKey = mGroupManager.getGroupKey(entry.getSbn());
             GroupAlertEntry groupAlertEntry = mGroupAlertEntries.get(groupKey);
-            if (groupAlertEntry != null && groupAlertEntry.mGroup.alertOverride == null) {
-                // new pending group entries require us to transfer back from the child to the
-                // group, but alertOverrides are only present in very limited circumstances, so
-                // while it's possible the group should ALSO alert, the previous detection which set
-                // this alertOverride won't be invalidated by this notification added to this group.
+            if (groupAlertEntry != null) {
                 checkShouldTransferBack(groupAlertEntry);
             }
         }
@@ -330,128 +262,43 @@
     }
 
     /**
-     * Handles the scenario where a summary that has been suppressed is itself, or has a former
-     * alertOverride (in the form of an isolated logical child) which was alerted.  A suppressed
+     * Handles the scenario where a summary that has been suppressed is alerted.  A suppressed
      * summary should for all intents and purposes be invisible to the user and as a result should
      * not alert.  When this is the case, it is our responsibility to pass the alert to the
      * appropriate child which will be the representative notification alerting for the group.
      *
-     * @param summary the summary that is suppressed and (potentially) alerting
-     * @param oldAlertOverride the alertOverride before whatever event triggered this method.  If
-     *                         the alert override was removed, this will be the entry that should
-     *                         be transferred back from.
+     * @param summary the summary that is suppressed and alerting
+     * @param alertManager the alert manager that manages the alerting summary
      */
     private void handleSuppressedSummaryAlerted(@NonNull NotificationEntry summary,
-            NotificationEntry oldAlertOverride) {
-        if (DEBUG) {
-            Log.d(TAG, "handleSuppressedSummaryAlerted: summary=" + summary);
-        }
+            @NonNull AlertingNotificationManager alertManager) {
+        StatusBarNotification sbn = summary.getSbn();
         GroupAlertEntry groupAlertEntry =
-                mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
-
+                mGroupAlertEntries.get(mGroupManager.getGroupKey(sbn));
         if (!mGroupManager.isSummaryOfSuppressedGroup(summary.getSbn())
+                || !alertManager.isAlerting(sbn.getKey())
                 || groupAlertEntry == null) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: invalid state");
-            }
-            return;
-        }
-        boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
-        boolean priorityIsAlerting = oldAlertOverride != null
-                && mHeadsUpManager.isAlerting(oldAlertOverride.getKey());
-        if (!summaryIsAlerting && !priorityIsAlerting) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: no summary or override alerting");
-            }
             return;
         }
 
         if (pendingInflationsWillAddChildren(groupAlertEntry.mGroup)) {
             // New children will actually be added to this group, let's not transfer the alert.
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: pending inflations");
-            }
             return;
         }
 
         NotificationEntry child =
                 mGroupManager.getLogicalChildren(summary.getSbn()).iterator().next();
-        if (summaryIsAlerting) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: transfer summary -> child");
+        if (child != null) {
+            if (child.getRow().keepInParent()
+                    || child.isRowRemoved()
+                    || child.isRowDismissed()) {
+                // The notification is actually already removed. No need to alert it.
+                return;
             }
-            tryTransferAlertState(summary, /*from*/ summary, /*to*/ child, groupAlertEntry);
-            return;
-        }
-        // Summary didn't have the alert, so we're in "transfer back" territory.  First, make sure
-        // it's not too late to transfer back, then transfer the alert from the oldAlertOverride to
-        // the isolated child which should receive the alert.
-        if (!canStillTransferBack(groupAlertEntry)) {
-            if (DEBUG) {
-                Log.d(TAG, "handleSuppressedSummaryAlerted: transfer from override: too late");
+            if (!alertManager.isAlerting(child.getKey()) && onlySummaryAlerts(summary)) {
+                groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
             }
-            return;
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, "handleSuppressedSummaryAlerted: transfer override -> child");
-        }
-        tryTransferAlertState(summary, /*from*/ oldAlertOverride, /*to*/ child, groupAlertEntry);
-    }
-
-    /**
-     * Checks for and handles the scenario where the given entry is the summary of a group which
-     * has an alertOverride, and either the summary itself or one of its logical isolated children
-     * is currently alerting (which happens if the summary is suppressed).
-     */
-    private void handleOverriddenSummaryAlerted(NotificationEntry summary) {
-        if (DEBUG) {
-            Log.d(TAG, "handleOverriddenSummaryAlerted: summary=" + summary);
-        }
-        GroupAlertEntry groupAlertEntry =
-                mGroupAlertEntries.get(mGroupManager.getGroupKey(summary.getSbn()));
-        NotificationGroup group = mGroupManager.getGroupForSummary(summary.getSbn());
-        if (group == null || group.alertOverride == null || groupAlertEntry == null) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: invalid state");
-            }
-            return;
-        }
-        boolean summaryIsAlerting = mHeadsUpManager.isAlerting(summary.getKey());
-        if (summaryIsAlerting) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: transfer summary -> override");
-            }
-            tryTransferAlertState(summary, /*from*/ summary, group.alertOverride, groupAlertEntry);
-            return;
-        }
-        // Summary didn't have the alert, so we're in "transfer back" territory.  First, make sure
-        // it's not too late to transfer back, then remove the alert from any of the logical
-        // children, and if one of them was alerting, we can alert the override.
-        if (!canStillTransferBack(groupAlertEntry)) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: transfer from child: too late");
-            }
-            return;
-        }
-        List<NotificationEntry> children = mGroupManager.getLogicalChildren(summary.getSbn());
-        if (children == null) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: no children");
-            }
-            return;
-        }
-        children.remove(group.alertOverride); // do not release the alert on our desired destination
-        boolean releasedChild = releaseChildAlerts(children);
-        if (releasedChild) {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: transfer child -> override");
-            }
-            tryTransferAlertState(summary, /*from*/ null, group.alertOverride, groupAlertEntry);
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "handleOverriddenSummaryAlerted: no child alert released");
-            }
+            transferAlertState(summary, child, alertManager);
         }
     }
 
@@ -460,37 +307,14 @@
      * immediately to have the incorrect one up as short as possible. The second should alert
      * when possible.
      *
-     * @param summary entry of the summary
      * @param fromEntry entry to transfer alert from
      * @param toEntry entry to transfer to
+     * @param alertManager alert manager for the alert type
      */
-    private void tryTransferAlertState(
-            NotificationEntry summary,
-            NotificationEntry fromEntry,
-            NotificationEntry toEntry,
-            GroupAlertEntry groupAlertEntry) {
-        if (toEntry != null) {
-            if (toEntry.getRow().keepInParent()
-                    || toEntry.isRowRemoved()
-                    || toEntry.isRowDismissed()) {
-                // The notification is actually already removed. No need to alert it.
-                return;
-            }
-            if (!mHeadsUpManager.isAlerting(toEntry.getKey()) && onlySummaryAlerts(summary)) {
-                groupAlertEntry.mLastAlertTransferTime = SystemClock.elapsedRealtime();
-            }
-            if (DEBUG) {
-                Log.d(TAG, "transferAlertState: fromEntry=" + fromEntry + " toEntry=" + toEntry);
-            }
-            transferAlertState(fromEntry, toEntry);
-        }
-    }
-    private void transferAlertState(@Nullable NotificationEntry fromEntry,
-            @NonNull NotificationEntry toEntry) {
-        if (fromEntry != null) {
-            mHeadsUpManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
-        }
-        alertNotificationWhenPossible(toEntry);
+    private void transferAlertState(@NonNull NotificationEntry fromEntry, @NonNull NotificationEntry toEntry,
+            @NonNull AlertingNotificationManager alertManager) {
+        alertManager.removeNotification(fromEntry.getKey(), true /* releaseImmediately */);
+        alertNotificationWhenPossible(toEntry, alertManager);
     }
 
     /**
@@ -502,13 +326,11 @@
      * more children are coming. Thus, if a child is added within a certain timeframe after we
      * transfer, we back out and alert the summary again.
      *
-     * An alert can only transfer back within a small window of time after a transfer away from the
-     * summary to a child happened.
-     *
      * @param groupAlertEntry group alert entry to check
      */
     private void checkShouldTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
-        if (canStillTransferBack(groupAlertEntry)) {
+        if (SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
+                < ALERT_TRANSFER_TIMEOUT) {
             NotificationEntry summary = groupAlertEntry.mGroup.summary;
 
             if (!onlySummaryAlerts(summary)) {
@@ -516,17 +338,30 @@
             }
             ArrayList<NotificationEntry> children = mGroupManager.getLogicalChildren(
                     summary.getSbn());
-            int numActiveChildren = children.size();
+            int numChildren = children.size();
             int numPendingChildren = getPendingChildrenNotAlerting(groupAlertEntry.mGroup);
-            int numChildren = numActiveChildren + numPendingChildren;
+            numChildren += numPendingChildren;
             if (numChildren <= 1) {
                 return;
             }
-            boolean releasedChild = releaseChildAlerts(children);
+            boolean releasedChild = false;
+            for (int i = 0; i < children.size(); i++) {
+                NotificationEntry entry = children.get(i);
+                if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
+                    releasedChild = true;
+                    mHeadsUpManager.removeNotification(
+                            entry.getKey(), true /* releaseImmediately */);
+                }
+                if (mPendingAlerts.containsKey(entry.getKey())) {
+                    // This is the child that would've been removed if it was inflated.
+                    releasedChild = true;
+                    mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
+                }
+            }
             if (releasedChild && !mHeadsUpManager.isAlerting(summary.getKey())) {
-                boolean notifyImmediately = numActiveChildren > 1;
+                boolean notifyImmediately = (numChildren - numPendingChildren) > 1;
                 if (notifyImmediately) {
-                    alertNotificationWhenPossible(summary);
+                    alertNotificationWhenPossible(summary, mHeadsUpManager);
                 } else {
                     // Should wait until the pending child inflates before alerting.
                     groupAlertEntry.mAlertSummaryOnNextAddition = true;
@@ -536,61 +371,25 @@
         }
     }
 
-    private boolean canStillTransferBack(@NonNull GroupAlertEntry groupAlertEntry) {
-        return SystemClock.elapsedRealtime() - groupAlertEntry.mLastAlertTransferTime
-                < ALERT_TRANSFER_TIMEOUT;
-    }
-
-    private boolean releaseChildAlerts(List<NotificationEntry> children) {
-        boolean releasedChild = false;
-        if (SPEW) {
-            Log.d(TAG, "releaseChildAlerts: numChildren=" + children.size());
-        }
-        for (int i = 0; i < children.size(); i++) {
-            NotificationEntry entry = children.get(i);
-            if (SPEW) {
-                Log.d(TAG, "releaseChildAlerts: checking i=" + i + " entry=" + entry
-                        + " onlySummaryAlerts=" + onlySummaryAlerts(entry)
-                        + " isAlerting=" + mHeadsUpManager.isAlerting(entry.getKey())
-                        + " isPendingAlert=" + mPendingAlerts.containsKey(entry.getKey()));
-            }
-            if (onlySummaryAlerts(entry) && mHeadsUpManager.isAlerting(entry.getKey())) {
-                releasedChild = true;
-                mHeadsUpManager.removeNotification(
-                        entry.getKey(), true /* releaseImmediately */);
-            }
-            if (mPendingAlerts.containsKey(entry.getKey())) {
-                // This is the child that would've been removed if it was inflated.
-                releasedChild = true;
-                mPendingAlerts.get(entry.getKey()).mAbortOnInflation = true;
-            }
-        }
-        if (SPEW) {
-            Log.d(TAG, "releaseChildAlerts: didRelease=" + releasedChild);
-        }
-        return releasedChild;
-    }
-
     /**
      * Tries to alert the notification. If its content view is not inflated, we inflate and continue
      * when the entry finishes inflating the view.
      *
      * @param entry entry to show
+     * @param alertManager alert manager for the alert type
      */
-    private void alertNotificationWhenPossible(@NonNull NotificationEntry entry) {
-        @InflationFlag int contentFlag = mHeadsUpManager.getContentFlag();
+    private void alertNotificationWhenPossible(@NonNull NotificationEntry entry,
+            @NonNull AlertingNotificationManager alertManager) {
+        @InflationFlag int contentFlag = alertManager.getContentFlag();
         final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
         if ((params.getContentViews() & contentFlag) == 0) {
-            if (DEBUG) {
-                Log.d(TAG, "alertNotificationWhenPossible: async requestRebind entry=" + entry);
-            }
             mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
             params.requireContentViews(contentFlag);
             mRowContentBindStage.requestRebind(entry, en -> {
                 PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
                 if (alertInfo != null) {
                     if (alertInfo.isStillValid()) {
-                        alertNotificationWhenPossible(entry);
+                        alertNotificationWhenPossible(entry, mHeadsUpManager);
                     } else {
                         // The transfer is no longer valid. Free the content.
                         mRowContentBindStage.getStageParams(entry).markContentViewsFreeable(
@@ -601,16 +400,10 @@
             });
             return;
         }
-        if (mHeadsUpManager.isAlerting(entry.getKey())) {
-            if (DEBUG) {
-                Log.d(TAG, "alertNotificationWhenPossible: continue alerting entry=" + entry);
-            }
-            mHeadsUpManager.updateNotification(entry.getKey(), true /* alert */);
+        if (alertManager.isAlerting(entry.getKey())) {
+            alertManager.updateNotification(entry.getKey(), true /* alert */);
         } else {
-            if (DEBUG) {
-                Log.d(TAG, "alertNotificationWhenPossible: start alerting entry=" + entry);
-            }
-            mHeadsUpManager.showNotification(entry);
+            alertManager.showNotification(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 c79e503..9c08b89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -22,7 +22,6 @@
 import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
 import static androidx.constraintlayout.widget.ConstraintSet.START;
 
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -1730,7 +1729,6 @@
             return;
         }
         mExpectingSynthesizedDown = true;
-        InteractionJankMonitor.getInstance().begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
         onTrackingStarted();
         updatePanelExpanded();
     }
@@ -2698,6 +2696,7 @@
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
         mIsExpanding = false;
         mMediaHierarchyManager.setCollapsingShadeFromQS(false);
+        mMediaHierarchyManager.setQsExpanded(mQsExpanded);
         if (isFullyCollapsed()) {
             DejankUtils.postAfterTraversal(new Runnable() {
                 @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 4714c4b..2649309 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -42,6 +42,7 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -139,6 +140,7 @@
      */
     private boolean mInstantExpanding;
     private boolean mAnimateAfterExpanding;
+    private boolean mIsFlinging;
 
     PanelBar mBar;
 
@@ -596,6 +598,7 @@
             notifyExpandingFinished();
             return;
         }
+        mIsFlinging = true;
         mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
         ValueAnimator animator = createHeightAnimator(target);
         mFlingTarget = target;
@@ -635,6 +638,12 @@
             private boolean mCancelled;
 
             @Override
+            public void onAnimationStart(Animator animation) {
+                InteractionJankMonitor.getInstance()
+                        .begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+            }
+
+            @Override
             public void onAnimationCancel(Animator animation) {
                 mCancelled = true;
             }
@@ -679,6 +688,7 @@
     }
 
     private void onFlingEnd(boolean cancelled) {
+        mIsFlinging = false;
         setAnimator(null);
         mKeyguardStateController.notifyPanelFlingEnd();
         if (!cancelled) {
@@ -751,6 +761,16 @@
         if (isNaN(h)) {
             Log.wtf(TAG, "ExpandedHeight set to NaN");
         }
+        if (mAmbientState.isExpansionChanging()
+                && !mIsFlinging  // Fling already uses interpolated height from end of swipe
+                && !mAmbientState.isOnKeyguard()
+                && !mAmbientState.isDozing()
+                && !mAmbientState.isPulsing()) {
+            final float fraction = h / mView.getHeight();
+            final float interpolatedFraction = new PathInterpolator(0.2f, 0.8f, 0.8f, 1f)
+                    .getInterpolation(fraction);
+            h = interpolatedFraction * mView.getHeight();
+        }
         maybeOverScrollForShadeFlingOpen(h);
         if (mExpandLatencyTracking && h != 0f) {
             DejankUtils.postAfterTraversal(
@@ -1398,11 +1418,14 @@
                 case MotionEvent.ACTION_CANCEL:
                     addMovement(event);
                     endMotionEvent(event, x, y, false /* forceCancel */);
-                    InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
-                    if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-                        monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
-                    } else {
-                        monitor.cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+                    // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
+                    if (mHeightAnimator == null) {
+                        InteractionJankMonitor monitor = InteractionJankMonitor.getInstance();
+                        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
+                            monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+                        } else {
+                            monitor.cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
+                        }
                     }
                     break;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6ef4663..aaef739 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -148,6 +148,7 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.DelegateLaunchAnimatorController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.camera.CameraIntents;
@@ -2785,13 +2786,8 @@
         final boolean afterKeyguardGone = mActivityIntentHelper.wouldLaunchResolverActivity(
                 intent, mLockscreenUserManager.getCurrentUserId());
 
-        ActivityLaunchAnimator.Controller animController = null;
-        if (animationController != null) {
-            animController = dismissShade ? new StatusBarLaunchAnimatorController(
-                    animationController, this, true /* isLaunchForActivity */)
-                    : animationController;
-        }
-        final ActivityLaunchAnimator.Controller animCallbackForLambda = animController;
+        ActivityLaunchAnimator.Controller animController = wrapAnimationController(
+                animationController, dismissShade);
 
         // If we animate, we will dismiss the shade only once the animation is done. This is taken
         // care of by the StatusBarLaunchAnimationController.
@@ -2804,7 +2800,7 @@
             intent.addFlags(flags);
             int[] result = new int[]{ActivityManager.START_CANCELED};
 
-            mActivityLaunchAnimator.startIntentWithAnimation(animCallbackForLambda,
+            mActivityLaunchAnimator.startIntentWithAnimation(animController,
                     areLaunchAnimationsEnabled(), (adapter) -> {
                         ActivityOptions options = new ActivityOptions(
                                 getActivityOptions(mDisplayId, adapter));
@@ -2858,6 +2854,46 @@
                 afterKeyguardGone, true /* deferred */);
     }
 
+    @Nullable
+    private ActivityLaunchAnimator.Controller wrapAnimationController(
+            @Nullable ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
+        if (animationController == null) {
+            return null;
+        }
+
+        View rootView = animationController.getLaunchContainer().getRootView();
+        if (rootView == mSuperStatusBarViewFactory.getStatusBarWindowView()) {
+            // We are animating a view in the status bar. We have to make sure that the status bar
+            // window matches the full screen during the animation and that we are expanding the
+            // view below the other status bar text.
+            animationController.setLaunchContainer(
+                    mStatusBarWindowController.getLaunchAnimationContainer());
+
+            return new DelegateLaunchAnimatorController(animationController) {
+                @Override
+                public void onLaunchAnimationStart(boolean isExpandingFullyAbove) {
+                    getDelegate().onLaunchAnimationStart(isExpandingFullyAbove);
+                    mStatusBarWindowController.setLaunchAnimationRunning(true);
+                }
+
+                @Override
+                public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) {
+                    getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove);
+                    mStatusBarWindowController.setLaunchAnimationRunning(false);
+                }
+            };
+        }
+
+        if (dismissShade && rootView == mNotificationShadeWindowView) {
+            // We are animating a view in the shade. We have to make sure that we collapse it when
+            // the animation ends or is cancelled.
+            return new StatusBarLaunchAnimatorController(animationController, this,
+                    true /* isLaunchForActivity */);
+        }
+
+        return animationController;
+    }
+
     public void readyForKeyguardDone() {
         mStatusBarKeyguardViewManager.readyForKeyguardDone();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f403cc94..1ef84701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -591,6 +591,8 @@
 
         if (mStatusBar.isInLaunchTransition()
                 || mKeyguardStateController.isFlingingToDismissKeyguard()) {
+            final boolean wasFlingingToDismissKeyguard =
+                    mKeyguardStateController.isFlingingToDismissKeyguard();
             mStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
                 @Override
                 public void run() {
@@ -604,6 +606,11 @@
                 public void run() {
                     mStatusBar.hideKeyguard();
                     mNotificationShadeWindowController.setKeyguardFadingAway(false);
+
+                    if (wasFlingingToDismissKeyguard) {
+                        mStatusBar.finishKeyguardFadingAway();
+                    }
+
                     mViewMediatorCallback.keyguardGone();
                     executeAfterKeyguardGoneAction();
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 2f7278b..30b8c5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -29,6 +29,7 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -51,6 +52,7 @@
     private final State mCurrentState = new State();
 
     private ViewGroup mStatusBarView;
+    private ViewGroup mLaunchAnimationContainer;
     private WindowManager.LayoutParams mLp;
     private final WindowManager.LayoutParams mLpChanged;
 
@@ -62,6 +64,8 @@
         mWindowManager = windowManager;
         mSuperStatusBarViewFactory = superStatusBarViewFactory;
         mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView();
+        mLaunchAnimationContainer = mStatusBarView.findViewById(
+                R.id.status_bar_launch_animation_container);
         mLpChanged = new WindowManager.LayoutParams();
         mResources = resources;
 
@@ -124,13 +128,38 @@
         apply(mCurrentState);
     }
 
-    private void applyHeight() {
-        mLpChanged.height = mBarHeight;
+    /**
+     * Return the container in which we should run launch animations started from the status bar and
+     * expanding into the opening window.
+     *
+     * @see #setLaunchAnimationRunning
+     */
+    public ViewGroup getLaunchAnimationContainer() {
+        return mLaunchAnimationContainer;
+    }
+
+    /**
+     * Set whether a launch animation is currently running. If true, this will ensure that the
+     * window matches its parent height so that the animation is not clipped by the normal status
+     * bar height.
+     */
+    public void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) {
+        if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) {
+            return;
+        }
+
+        mCurrentState.mIsLaunchAnimationRunning = isLaunchAnimationRunning;
+        apply(mCurrentState);
+    }
+
+    private void applyHeight(State state) {
+        mLpChanged.height =
+                state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight;
     }
 
     private void apply(State state) {
         applyForceStatusBarVisibleFlag(state);
-        applyHeight();
+        applyHeight(state);
         if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
             mWindowManager.updateViewLayout(mStatusBarView, mLp);
         }
@@ -138,10 +167,11 @@
 
     private static class State {
         boolean mForceStatusBarVisible;
+        boolean mIsLaunchAnimationRunning;
     }
 
     private void applyForceStatusBarVisibleFlag(State state) {
-        if (state.mForceStatusBarVisible) {
+        if (state.mForceStatusBarVisible || state.mIsLaunchAnimationRunning) {
             mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
         } else {
             mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
index fbba09a..897d78b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt
@@ -11,7 +11,8 @@
 import android.view.RemoteAnimationAdapter
 import android.view.RemoteAnimationTarget
 import android.view.SurfaceControl
-import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import junit.framework.Assert.assertFalse
@@ -36,8 +37,8 @@
 @RunWithLooper
 class ActivityLaunchAnimatorTest : SysuiTestCase() {
     private val activityLaunchAnimator = ActivityLaunchAnimator(mContext)
-    private val rootView = View(mContext)
-    @Spy private val controller = TestLaunchAnimatorController(rootView)
+    private val launchContainer = LinearLayout(mContext)
+    @Spy private val controller = TestLaunchAnimatorController(launchContainer)
     @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
 
     @get:Rule val rule = MockitoJUnit.rule()
@@ -146,10 +147,8 @@
  * outside of the main thread.
  */
 private class TestLaunchAnimatorController(
-    private val rootView: View
+    override var launchContainer: ViewGroup
 ) : ActivityLaunchAnimator.Controller {
-    override fun getRootView(): View = rootView
-
     override fun createAnimatorState() = ActivityLaunchAnimator.State(
             top = 100,
             bottom = 200,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index d86dfa5..406f40c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
 import org.junit.Assert.assertNotNull
@@ -73,6 +74,8 @@
     private lateinit var mediaCarouselController: MediaCarouselController
     @Mock
     private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock
+    private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Captor
     private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
     @JvmField
@@ -90,7 +93,8 @@
                 bypassController,
                 mediaCarouselController,
                 notificationLockscreenUserManager,
-                wakefulnessLifecycle)
+                wakefulnessLifecycle,
+                statusBarKeyguardViewManager)
         verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
         setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
         setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
@@ -98,7 +102,6 @@
         `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
         // We'll use the viewmanager to verify a few calls below, let's reset this.
         clearInvocations(mediaCarouselController)
-
     }
 
     private fun setupHost(host: MediaHost, location: Int) {
@@ -125,7 +128,8 @@
         observer.onStartedGoingToSleep()
         clearInvocations(mediaCarouselController)
         mediaHiearchyManager.qsExpansion = 0.0f
-        verify(mediaCarouselController, times(0)).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
+        verify(mediaCarouselController, times(0))
+                .onDesiredLocationChanged(ArgumentMatchers.anyInt(),
                 any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
index b85af48..47f4183 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarRotationContextTest.java
@@ -62,8 +62,8 @@
 
         final View view = new View(mContext);
         mRotationButton = mock(RotationButton.class);
-        mRotationButtonController = spy(new RotationButtonController(mContext, 0, 0));
-        mRotationButtonController.setRotationButton(mRotationButton, (visibility) -> {});
+        mRotationButtonController = spy(new RotationButtonController(mContext, 0, 0,
+                mRotationButton, (visibility) -> {}));
         final KeyButtonDrawable kbd = mock(KeyButtonDrawable.class);
         doReturn(view).when(mRotationButton).getCurrentView();
         doReturn(true).when(mRotationButton).acceptRotationProposal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 68ed2a5..6f0ae22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -474,6 +474,22 @@
     }
 
     @Test
+    public void transientIndication_visibleWhenDozing_ignoresFingerprintCancellation() {
+        createController();
+
+        mController.setVisible(true);
+        reset(mRotateTextViewController);
+        mController.getKeyguardCallback().onBiometricError(
+                FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED, "foo",
+                BiometricSourceType.FINGERPRINT);
+        mController.getKeyguardCallback().onBiometricError(
+                FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar",
+                BiometricSourceType.FINGERPRINT);
+
+        verifyNoTransientMessage();
+    }
+
+    @Test
     public void transientIndication_swipeUpToRetry() {
         createController();
         String message = mContext.getString(R.string.keyguard_retry);
@@ -668,4 +684,8 @@
     private void verifyTransientMessage(String message) {
         verify(mRotateTextViewController).showTransient(eq(message), anyBoolean());
     }
+
+    private void verifyNoTransientMessage() {
+        verify(mRotateTextViewController, never()).showTransient(any(), anyBoolean());
+    }
 }
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/Android.bp b/packages/overlays/AvoidAppsInCutoutOverlay/Android.bp
new file mode 100644
index 0000000..4352c04
--- /dev/null
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/Android.bp
@@ -0,0 +1,29 @@
+//
+//  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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+runtime_resource_overlay {
+    name: "AvoidAppsInCutoutOverlay",
+    theme: "DisplayCutoutAvoidAppsInCutout",
+    product_specific: true,
+}
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/AndroidManifest.xml b/packages/overlays/AvoidAppsInCutoutOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..e63c5c3
--- /dev/null
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.internal.display.cutout.emulation.avoidAppsInCutout"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+            android:category="com.android.internal.display_cutout_emulation"
+            android:priority="0"/>
+
+    <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
+</manifest>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values/config.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values/config.xml
new file mode 100644
index 0000000..22eabf2
--- /dev/null
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values/config.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Whether the display cutout region of the main built-in display should be forced to
+         black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+     -->
+    <bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+
+    <!-- If true, hide the display cutout with display area -->
+    <bool name="config_hideDisplayCutoutWithDisplayArea">true</bool>
+
+</resources>
diff --git a/packages/overlays/AvoidAppsInCutoutOverlay/res/values/strings.xml b/packages/overlays/AvoidAppsInCutoutOverlay/res/values/strings.xml
new file mode 100644
index 0000000..a65fd43
--- /dev/null
+++ b/packages/overlays/AvoidAppsInCutoutOverlay/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- [CHAR_LIMIT=NONE] Developer Settings: Label for the option that masks the display cutout, i.e. avoid apps in cutout region.-->
+    <string name="display_cutout_emulation_overlay">Hide (avoid apps in cutout region)</string>
+
+</resources>
+
diff --git a/packages/overlays/NoCutoutOverlay/Android.bp b/packages/overlays/NoCutoutOverlay/Android.bp
new file mode 100644
index 0000000..78f5627
--- /dev/null
+++ b/packages/overlays/NoCutoutOverlay/Android.bp
@@ -0,0 +1,30 @@
+//
+//  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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+runtime_resource_overlay {
+    name: "NoCutoutOverlay",
+    theme: "DisplayCutoutNoCutout",
+    product_specific: true,
+}
diff --git a/packages/overlays/NoCutoutOverlay/AndroidManifest.xml b/packages/overlays/NoCutoutOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..c622496
--- /dev/null
+++ b/packages/overlays/NoCutoutOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.internal.display.cutout.emulation.noCutout"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+            android:category="com.android.internal.display_cutout_emulation"
+            android:priority="0"/>
+
+    <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
+</manifest>
diff --git a/packages/overlays/NoCutoutOverlay/res/values/config.xml b/packages/overlays/NoCutoutOverlay/res/values/config.xml
new file mode 100644
index 0000000..9157699
--- /dev/null
+++ b/packages/overlays/NoCutoutOverlay/res/values/config.xml
@@ -0,0 +1,36 @@
+<!--
+  ~ 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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Whether the display cutout region of the main built-in display should be forced to
+         black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+     -->
+    <bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+
+    <!-- If true, and there is a cutout on the main built in display, the cutout will be masked
+         by shrinking the display such that it does not overlap the cutout area. -->
+    <bool name="config_maskMainBuiltInDisplayCutout">true</bool>
+
+    <!-- Height of the status bar -->
+    <dimen name="status_bar_height_portrait">28dp</dimen>
+    <dimen name="status_bar_height_landscape">28dp</dimen>
+
+    <!-- Height of area above QQS where battery/time go (equal to status bar height if > 48dp) -->
+    <dimen name="quick_qs_offset_height">48dp</dimen>
+    <!-- Total height of QQS (quick_qs_offset_height + 128) -->
+    <dimen name="quick_qs_total_height">176dp</dimen>
+</resources>
diff --git a/packages/overlays/NoCutoutOverlay/res/values/strings.xml b/packages/overlays/NoCutoutOverlay/res/values/strings.xml
new file mode 100644
index 0000000..dd01ada
--- /dev/null
+++ b/packages/overlays/NoCutoutOverlay/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- [CHAR_LIMIT=NONE] Developer Settings: Label for the option that masks the display cutout,
+         i.e. it shrinks the display such that the display cutout is no longer visible.-->
+    <string name="display_cutout_emulation_overlay">Hide</string>
+
+</resources>
+
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index ceb12c8..9e8b9c6 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -36,6 +36,15 @@
                 }
             ],
             "file_patterns": ["ClipboardService\\.java"]
+        },
+        {
+            "name": "FrameworksMockingServicesTests",
+            "options": [
+                {
+                    "include-filter": "com.android.server.sensorprivacy"
+                }
+            ],
+            "file_patterns": ["SensorPrivacyService\\.java"]
         }
     ],
     "presubmit-large": [
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 206f135..5700bb36 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5934,7 +5934,7 @@
      */
     private void logForegroundServiceStateChanged(ServiceRecord r, int state, int durationMs) {
         if (!ActivityManagerUtils.shouldSamplePackageForAtom(
-                r.packageName, mAm.mConstants.mDefaultFgsAtomSampleRate)) {
+                r.packageName, mAm.mConstants.mFgsAtomSampleRate)) {
             return;
         }
         FrameworkStatsLog.write(FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED,
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index d8eccef..f7ce6dd 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -73,10 +73,16 @@
     private static final String KEY_POWER_CHECK_MAX_CPU_2 = "power_check_max_cpu_2";
     private static final String KEY_POWER_CHECK_MAX_CPU_3 = "power_check_max_cpu_3";
     private static final String KEY_POWER_CHECK_MAX_CPU_4 = "power_check_max_cpu_4";
-    private static final String KEY_SERVICE_USAGE_INTERACTION_TIME
-            = "service_usage_interaction_time";
-    private static final String KEY_USAGE_STATS_INTERACTION_INTERVAL
-            = "usage_stats_interaction_interval";
+    /** Used for all apps on R and earlier versions. */
+    private static final String KEY_SERVICE_USAGE_INTERACTION_TIME_PRE_S =
+            "service_usage_interaction_time";
+    private static final String KEY_SERVICE_USAGE_INTERACTION_TIME_POST_S =
+            "service_usage_interaction_time_post_s";
+    /** Used for all apps on R and earlier versions. */
+    private static final String KEY_USAGE_STATS_INTERACTION_INTERVAL_PRE_S =
+            "usage_stats_interaction_interval";
+    private static final String KEY_USAGE_STATS_INTERACTION_INTERVAL_POST_S =
+            "usage_stats_interaction_interval_post_s";
     private static final String KEY_IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES =
             "imperceptible_kill_exempt_packages";
     private static final String KEY_IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES =
@@ -120,8 +126,10 @@
     private static final int DEFAULT_POWER_CHECK_MAX_CPU_2 = 25;
     private static final int DEFAULT_POWER_CHECK_MAX_CPU_3 = 10;
     private static final int DEFAULT_POWER_CHECK_MAX_CPU_4 = 2;
-    private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
-    private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL = 2*60*60*1000L;
+    private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME_PRE_S = 30 * 60 * 1000;
+    private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME_POST_S = 60 * 1000;
+    private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_PRE_S = 2 * 60 * 60 * 1000;
+    private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_POST_S = 10 * 60 * 1000;
     private static final long DEFAULT_SERVICE_RESTART_DURATION = 1*1000;
     private static final long DEFAULT_SERVICE_RESET_RUN_DURATION = 60*1000;
     private static final int DEFAULT_SERVICE_RESTART_DURATION_FACTOR = 4;
@@ -303,11 +311,23 @@
 
     // This is the amount of time an app needs to be running a foreground service before
     // we will consider it to be doing interaction for usage stats.
-    long SERVICE_USAGE_INTERACTION_TIME = DEFAULT_SERVICE_USAGE_INTERACTION_TIME;
+    // Only used for apps targeting pre-S versions.
+    long SERVICE_USAGE_INTERACTION_TIME_PRE_S = DEFAULT_SERVICE_USAGE_INTERACTION_TIME_PRE_S;
+
+    // This is the amount of time an app needs to be running a foreground service before
+    // we will consider it to be doing interaction for usage stats.
+    // Only used for apps targeting versions S and above.
+    long SERVICE_USAGE_INTERACTION_TIME_POST_S = DEFAULT_SERVICE_USAGE_INTERACTION_TIME_POST_S;
 
     // Maximum amount of time we will allow to elapse before re-reporting usage stats
     // interaction with foreground processes.
-    long USAGE_STATS_INTERACTION_INTERVAL = DEFAULT_USAGE_STATS_INTERACTION_INTERVAL;
+    // Only used for apps targeting pre-S versions.
+    long USAGE_STATS_INTERACTION_INTERVAL_PRE_S = DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
+
+    // Maximum amount of time we will allow to elapse before re-reporting usage stats
+    // interaction with foreground processes.
+    // Only used for apps targeting versions S and above.
+    long USAGE_STATS_INTERACTION_INTERVAL_POST_S = DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_POST_S;
 
     // How long a service needs to be running until restarting its process
     // is no longer considered to be a relaunch of the service.
@@ -460,7 +480,7 @@
      *
      * If the value is 0.1, 10% of the installed packages would be sampled.
      */
-    volatile float mDefaultFgsAtomSampleRate = DEFAULT_FGS_ATOM_SAMPLE_RATE;
+    volatile float mFgsAtomSampleRate = DEFAULT_FGS_ATOM_SAMPLE_RATE;
 
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
@@ -817,10 +837,18 @@
                     DEFAULT_POWER_CHECK_MAX_CPU_3);
             POWER_CHECK_MAX_CPU_4 = mParser.getInt(KEY_POWER_CHECK_MAX_CPU_4,
                     DEFAULT_POWER_CHECK_MAX_CPU_4);
-            SERVICE_USAGE_INTERACTION_TIME = mParser.getLong(KEY_SERVICE_USAGE_INTERACTION_TIME,
-                    DEFAULT_SERVICE_USAGE_INTERACTION_TIME);
-            USAGE_STATS_INTERACTION_INTERVAL = mParser.getLong(KEY_USAGE_STATS_INTERACTION_INTERVAL,
-                    DEFAULT_USAGE_STATS_INTERACTION_INTERVAL);
+            SERVICE_USAGE_INTERACTION_TIME_PRE_S = mParser.getLong(
+                    KEY_SERVICE_USAGE_INTERACTION_TIME_PRE_S,
+                    DEFAULT_SERVICE_USAGE_INTERACTION_TIME_PRE_S);
+            SERVICE_USAGE_INTERACTION_TIME_POST_S = mParser.getLong(
+                    KEY_SERVICE_USAGE_INTERACTION_TIME_POST_S,
+                    DEFAULT_SERVICE_USAGE_INTERACTION_TIME_POST_S);
+            USAGE_STATS_INTERACTION_INTERVAL_PRE_S = mParser.getLong(
+                    KEY_USAGE_STATS_INTERACTION_INTERVAL_PRE_S,
+                    DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_PRE_S);
+            USAGE_STATS_INTERACTION_INTERVAL_POST_S = mParser.getLong(
+                    KEY_USAGE_STATS_INTERACTION_INTERVAL_POST_S,
+                    DEFAULT_USAGE_STATS_INTERACTION_INTERVAL_POST_S);
             SERVICE_RESTART_DURATION = mParser.getLong(KEY_SERVICE_RESTART_DURATION,
                     DEFAULT_SERVICE_RESTART_DURATION);
             SERVICE_RESET_RUN_DURATION = mParser.getLong(KEY_SERVICE_RESET_RUN_DURATION,
@@ -985,7 +1013,7 @@
     }
 
     private void updateFgsAtomSamplePercent() {
-        mDefaultFgsAtomSampleRate = DeviceConfig.getFloat(
+        mFgsAtomSampleRate = DeviceConfig.getFloat(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 KEY_FGS_ATOM_SAMPLE_RATE,
                 DEFAULT_FGS_ATOM_SAMPLE_RATE);
@@ -1135,10 +1163,14 @@
         pw.println(POWER_CHECK_MAX_CPU_3);
         pw.print("  "); pw.print(KEY_POWER_CHECK_MAX_CPU_4); pw.print("=");
         pw.println(POWER_CHECK_MAX_CPU_4);
-        pw.print("  "); pw.print(KEY_SERVICE_USAGE_INTERACTION_TIME); pw.print("=");
-        pw.println(SERVICE_USAGE_INTERACTION_TIME);
-        pw.print("  "); pw.print(KEY_USAGE_STATS_INTERACTION_INTERVAL); pw.print("=");
-        pw.println(USAGE_STATS_INTERACTION_INTERVAL);
+        pw.print("  "); pw.print(KEY_SERVICE_USAGE_INTERACTION_TIME_PRE_S); pw.print("=");
+        pw.println(SERVICE_USAGE_INTERACTION_TIME_PRE_S);
+        pw.print("  "); pw.print(KEY_SERVICE_USAGE_INTERACTION_TIME_POST_S); pw.print("=");
+        pw.println(SERVICE_USAGE_INTERACTION_TIME_POST_S);
+        pw.print("  "); pw.print(KEY_USAGE_STATS_INTERACTION_INTERVAL_PRE_S); pw.print("=");
+        pw.println(USAGE_STATS_INTERACTION_INTERVAL_PRE_S);
+        pw.print("  "); pw.print(KEY_USAGE_STATS_INTERACTION_INTERVAL_POST_S); pw.print("=");
+        pw.println(USAGE_STATS_INTERACTION_INTERVAL_POST_S);
         pw.print("  "); pw.print(KEY_SERVICE_RESTART_DURATION); pw.print("=");
         pw.println(SERVICE_RESTART_DURATION);
         pw.print("  "); pw.print(KEY_SERVICE_RESET_RUN_DURATION); pw.print("=");
@@ -1204,7 +1236,7 @@
         pw.print("  "); pw.print(KEY_DEFAULT_FGS_STARTS_RESTRICTION_CHECK_CALLER_TARGET_SDK);
         pw.print("="); pw.println(mFgsStartRestrictionCheckCallerTargetSdk);
         pw.print("  "); pw.print(KEY_FGS_ATOM_SAMPLE_RATE);
-        pw.print("="); pw.println(mDefaultFgsAtomSampleRate);
+        pw.print("="); pw.println(mFgsAtomSampleRate);
         pw.print("  "); pw.print(KEY_PUSH_MESSAGING_OVER_QUOTA_BEHAVIOR);
         pw.print("="); pw.println(mPushMessagingOverQuotaBehavior);
 
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 649d050..b413010 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -80,6 +80,7 @@
 import android.app.usage.UsageEvents;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.EnabledSince;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -122,7 +123,7 @@
 /**
  * All of the code required to compute proc states and oom_adj values.
  */
-public final class OomAdjuster {
+public class OomAdjuster {
     static final String TAG = "OomAdjuster";
     static final String OOM_ADJ_REASON_METHOD = "updateOomAdj";
     static final String OOM_ADJ_REASON_NONE = OOM_ADJ_REASON_METHOD + "_meh";
@@ -164,6 +165,14 @@
     static final long CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID = 136219221L;
 
     /**
+     * For apps targeting S+, this determines whether to use a shorter timeout before elevating the
+     * standby bucket to ACTIVE when apps start a foreground service.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
+    static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
+
+    /**
      * For some direct access we need to power manager.
      */
     PowerManagerInternal mLocalPowerManager;
@@ -249,7 +258,9 @@
 
     private final PlatformCompatCache mPlatformCompatCache;
 
-    private static class PlatformCompatCache {
+    /** Overrideable by a test */
+    @VisibleForTesting
+    static class PlatformCompatCache {
         private final PlatformCompat mPlatformCompat;
         private final IPlatformCompat mIPlatformCompatProxy;
         private final LongSparseArray<CacheItem> mCaches = new LongSparseArray<>();
@@ -278,6 +289,20 @@
                     : mIPlatformCompatProxy.isChangeEnabled(changeId, app);
         }
 
+        /**
+         * Same as {@link #isChangeEnabled(long, ApplicationInfo)} but instead of throwing a
+         * RemoteException from platform compat, it returns the default value provided.
+         */
+        boolean isChangeEnabled(long changeId, ApplicationInfo app, boolean defaultValue) {
+            try {
+                return mCacheEnabled ? mCaches.get(changeId).isChangeEnabled(app)
+                        : mIPlatformCompatProxy.isChangeEnabled(changeId, app);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Error reading platform compat change " + changeId, e);
+                return defaultValue;
+            }
+        }
+
         void invalidate(ApplicationInfo app) {
             for (int i = mCaches.size() - 1; i >= 0; i--) {
                 mCaches.valueAt(i).invalidate(app);
@@ -335,6 +360,12 @@
         }
     }
 
+    /** Overrideable by a test */
+    @VisibleForTesting
+    protected PlatformCompatCache getPlatformCompatCache() {
+        return mPlatformCompatCache;
+    }
+
     OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
         this(service, processList, activeUids, createAdjusterThread());
     }
@@ -383,7 +414,8 @@
         mNumSlots = ((ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ + 1) >> 1)
                 / ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
         mPlatformCompatCache = new PlatformCompatCache(new long[] {
-                PROCESS_CAPABILITY_CHANGE_ID, CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID
+                PROCESS_CAPABILITY_CHANGE_ID, CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
+                USE_SHORT_FGS_USAGE_INTERACTION_TIME
         });
     }
 
@@ -755,7 +787,7 @@
         if (app != null) {
             mPendingProcessSet.remove(app);
             if (procDied) {
-                mPlatformCompatCache.invalidate(app.info);
+                getPlatformCompatCache().invalidate(app.info);
             }
         }
     }
@@ -1924,7 +1956,7 @@
 
                     boolean enabled = false;
                     try {
-                        enabled = mPlatformCompatCache.isChangeEnabled(
+                        enabled = getPlatformCompatCache().isChangeEnabled(
                                 CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID, s.appInfo);
                     } catch (RemoteException e) {
                     }
@@ -2147,7 +2179,7 @@
                                 state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
                                 boolean enabled = false;
                                 try {
-                                    enabled = mPlatformCompatCache.isChangeEnabled(
+                                    enabled = getPlatformCompatCache().isChangeEnabled(
                                             PROCESS_CAPABILITY_CHANGE_ID, client.info);
                                 } catch (RemoteException e) {
                                 }
@@ -2787,15 +2819,27 @@
             } else {
                 state.setProcStateChanged(true);
             }
-        } else if (state.hasReportedInteraction() && (nowElapsed - state.getInteractionEventTime())
-                > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
+        } else if (state.hasReportedInteraction()) {
+            final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
+                    USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+            final long interactionThreshold = fgsInteractionChangeEnabled
+                    ? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
+                    : mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
             // For apps that sit around for a long time in the interactive state, we need
             // to report this at least once a day so they don't go idle.
-            maybeUpdateUsageStatsLSP(app, nowElapsed);
-        } else if (!state.hasReportedInteraction() && (nowElapsed - state.getFgInteractionTime())
-                > mConstants.SERVICE_USAGE_INTERACTION_TIME) {
+            if ((nowElapsed - state.getInteractionEventTime()) > interactionThreshold) {
+                maybeUpdateUsageStatsLSP(app, nowElapsed);
+            }
+        } else {
+            final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
+                    USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+            final long interactionThreshold = fgsInteractionChangeEnabled
+                    ? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
+                    : mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
             // For foreground services that sit around for a long time but are not interacted with.
-            maybeUpdateUsageStatsLSP(app, nowElapsed);
+            if ((nowElapsed - state.getFgInteractionTime()) > interactionThreshold) {
+                maybeUpdateUsageStatsLSP(app, nowElapsed);
+            }
         }
 
         if (state.getCurCapability() != state.getSetCapability()) {
@@ -2877,6 +2921,8 @@
         if (mService.mUsageStatsService == null) {
             return;
         }
+        final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
+                USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
         boolean isInteraction;
         // To avoid some abuse patterns, we are going to be careful about what we consider
         // to be an app interaction.  Being the top activity doesn't count while the display
@@ -2890,18 +2936,22 @@
                 state.setFgInteractionTime(nowElapsed);
                 isInteraction = false;
             } else {
-                isInteraction = nowElapsed > state.getFgInteractionTime()
-                        + mConstants.SERVICE_USAGE_INTERACTION_TIME;
+                final long interactionTime = fgsInteractionChangeEnabled
+                        ? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
+                        : mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
+                isInteraction = nowElapsed > state.getFgInteractionTime() + interactionTime;
             }
         } else {
             isInteraction =
                     state.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
             state.setFgInteractionTime(0);
         }
+        final long interactionThreshold = fgsInteractionChangeEnabled
+                ? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
+                : mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
         if (isInteraction
                 && (!state.hasReportedInteraction()
-                    || (nowElapsed - state.getInteractionEventTime())
-                    > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
+                    || (nowElapsed - state.getInteractionEventTime()) > interactionThreshold)) {
             state.setInteractionEventTime(nowElapsed);
             String[] packages = app.getPackageList();
             if (packages != null) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 6765ad0..cc98abf 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -135,6 +135,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.wm.ActivityServiceConnectionsHolder;
 import com.android.server.wm.WindowManagerService;
+import com.android.server.wm.WindowProcessController;
 
 import dalvik.system.VMRuntime;
 
@@ -4626,6 +4627,7 @@
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     void updateApplicationInfoLOSP(List<String> packagesToUpdate, int userId,
             boolean updateFrameworkRes) {
+        final ArrayList<WindowProcessController> targetProcesses = new ArrayList<>();
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             final ProcessRecord app = mLruProcesses.get(i);
             if (app.getThread() == null) {
@@ -4646,6 +4648,7 @@
                             if (ai.packageName.equals(app.info.packageName)) {
                                 app.info = ai;
                             }
+                            targetProcesses.add(app.getWindowProcessController());
                         }
                     } catch (RemoteException e) {
                         Slog.w(TAG, String.format("Failed to update %s ApplicationInfo for %s",
@@ -4654,6 +4657,9 @@
                 }
             });
         }
+
+        mService.mActivityTaskManager.updateAssetConfiguration(
+                updateFrameworkRes ? null : targetProcesses);
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index f566080..a139589 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -257,8 +257,8 @@
             return false;
         }
 
-        // don't allow attention check in screen off state
-        if (!mPowerManager.isInteractive()) {
+        // don't allow attention check in screen off state or power save mode
+        if (!mPowerManager.isInteractive() || mPowerManager.isPowerSaveMode()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 39b7a74..ce06d06 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -811,7 +811,7 @@
             for (String instance : instances) {
                 final String fqName = IFingerprint.DESCRIPTOR + "/" + instance;
                 final IFingerprint fp = IFingerprint.Stub.asInterface(
-                        ServiceManager.waitForDeclaredService(fqName));
+                        Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
                 if (fp == null) {
                     Slog.e(TAG, "Unable to get declared service: " + fqName);
                     continue;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index c23c113..083df19 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -37,6 +37,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -186,8 +187,9 @@
         Slog.d(getTag(), "Daemon was null, reconnecting");
 
         mDaemon = IFingerprint.Stub.asInterface(
-                ServiceManager.waitForDeclaredService(IFingerprint.DESCRIPTOR
-                        + "/" + mHalInstanceName));
+                Binder.allowBlocking(
+                        ServiceManager.waitForDeclaredService(
+                                IFingerprint.DESCRIPTOR + "/" + mHalInstanceName)));
         if (mDaemon == null) {
             Slog.e(getTag(), "Unable to get daemon");
             return null;
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 62de369..e604a05 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -167,6 +167,28 @@
         return enabled;
     }
 
+    /**
+     * Called by the package manager to check if a given change is enabled for a given package name
+     * and the target sdk version while the package is in the parsing state.
+     *
+     * <p>Does not perform costly permission check.
+     *
+     * @param changeId the ID of the change in question
+     * @param packageName package name to check for
+     * @param targetSdkVersion target sdk version to check for
+     * @return {@code true} if the change would be enabled for this package name.
+     */
+    public boolean isChangeEnabledInternal(long changeId, String packageName,
+            int targetSdkVersion) {
+        if (mCompatConfig.willChangeBeEnabled(changeId, packageName)) {
+            final ApplicationInfo appInfo = new ApplicationInfo();
+            appInfo.packageName = packageName;
+            appInfo.targetSdkVersion = targetSdkVersion;
+            return isChangeEnabledInternalNoLogging(changeId, appInfo);
+        }
+        return false;
+    }
+
     @Override
     public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
         checkCompatChangeOverridePermission();
diff --git a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
index 78c4144..5a0069a 100644
--- a/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
+++ b/services/core/java/com/android/server/inputmethod/InputContentUriTokenHandler.java
@@ -104,7 +104,8 @@
     }
 
     /**
-     * {@inheritDoc}
+     * If permissions are not released explicitly via {@link #release()}, release automatically
+     * whenever there are no more references to this object.
      */
     @Override
     protected void finalize() throws Throwable {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 864aa33..172a68a 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -171,8 +171,7 @@
             // client caching behavior is only enabled after seeing the first invalidate
             LocationManager.invalidateLocalLocationEnabledCaches();
             // disable caching for our own process
-            Objects.requireNonNull(getContext().getSystemService(LocationManager.class))
-                    .disableLocalLocationEnabledCaches();
+            LocationManager.disableLocalLocationEnabledCaches();
         }
 
         @Override
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 0be325f..4b772f2 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -2271,22 +2271,28 @@
         }
 
         if (mOnLocationTagsChangeListener != null) {
-            if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags)) {
+            if (!oldState.extraAttributionTags.equals(newState.extraAttributionTags)
+                    || !Objects.equals(oldState.identity, newState.identity)) {
                 if (oldState.identity != null) {
                     FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                             OnProviderLocationTagsChangeListener::onLocationTagsChanged,
                             mOnLocationTagsChangeListener, new LocationTagInfo(
                                     oldState.identity.getUid(), oldState.identity.getPackageName(),
                                     Collections.emptySet())
-                            ));
+                    ));
                 }
                 if (newState.identity != null) {
+                    ArraySet<String> attributionTags = new ArraySet<>(
+                            newState.extraAttributionTags.size() + 1);
+                    attributionTags.addAll(newState.extraAttributionTags);
+                    attributionTags.add(newState.identity.getAttributionTag());
+
                     FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                             OnProviderLocationTagsChangeListener::onLocationTagsChanged,
                             mOnLocationTagsChangeListener, new LocationTagInfo(
                                     newState.identity.getUid(), newState.identity.getPackageName(),
-                                    newState.extraAttributionTags)
-                            ));
+                                    attributionTags)
+                    ));
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 656f347..b6f5e99 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -20,6 +20,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -88,6 +89,7 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.utils.RequestThrottle;
 
 import libcore.io.IoUtils;
 
@@ -220,6 +222,14 @@
         }
     }
 
+    @NonNull
+    private final RequestThrottle mSettingsWriteRequest = new RequestThrottle(IoThread.getHandler(),
+            () -> {
+                synchronized (mSessions) {
+                    return writeSessionsLocked();
+                }
+            });
+
     public PackageInstallerService(Context context, PackageManagerService pm,
             Supplier<PackageParser2> apexParserSupplier) {
         mContext = context;
@@ -275,7 +285,7 @@
 
             // Invalid sessions might have been marked while parsing. Re-write the database with
             // the updated information.
-            writeSessionsLocked();
+            mSettingsWriteRequest.runNow();
 
         }
     }
@@ -464,7 +474,7 @@
     }
 
     @GuardedBy("mSessions")
-    private void writeSessionsLocked() {
+    private boolean writeSessionsLocked() {
         if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
 
         FileOutputStream fos = null;
@@ -483,28 +493,20 @@
             out.endDocument();
 
             mSessionsFile.finishWrite(fos);
+            return true;
         } catch (IOException e) {
             if (fos != null) {
                 mSessionsFile.failWrite(fos);
             }
         }
+
+        return false;
     }
 
     private File buildAppIconFile(int sessionId) {
         return new File(mSessionsDir, "app_icon." + sessionId + ".png");
     }
 
-    private void writeSessionsAsync() {
-        IoThread.getHandler().post(new Runnable() {
-            @Override
-            public void run() {
-                synchronized (mSessions) {
-                    writeSessionsLocked();
-                }
-            }
-        });
-    }
-
     @Override
     public int createSession(SessionParams params, String installerPackageName,
             String callingAttributionTag, int userId) {
@@ -764,7 +766,7 @@
 
         mCallbacks.notifySessionCreated(session.sessionId, session.userId);
 
-        writeSessionsAsync();
+        mSettingsWriteRequest.schedule();
         return sessionId;
     }
 
@@ -1374,7 +1376,7 @@
     class InternalCallback {
         public void onSessionBadgingChanged(PackageInstallerSession session) {
             mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId);
-            writeSessionsAsync();
+            mSettingsWriteRequest.schedule();
         }
 
         public void onSessionActiveChanged(PackageInstallerSession session, boolean active) {
@@ -1389,7 +1391,7 @@
 
         public void onStagedSessionChanged(PackageInstallerSession session) {
             session.markUpdated();
-            writeSessionsAsync();
+            mSettingsWriteRequest.schedule();
             if (mOkToSendBroadcasts && !session.isDestroyed()) {
                 // we don't scrub the data here as this is sent only to the installer several
                 // privileged system packages
@@ -1419,7 +1421,7 @@
                             appIconFile.delete();
                         }
 
-                        writeSessionsLocked();
+                        mSettingsWriteRequest.runNow();
                     }
                 }
             });
@@ -1428,16 +1430,14 @@
         public void onSessionPrepared(PackageInstallerSession session) {
             // We prepared the destination to write into; we want to persist
             // this, but it's not critical enough to block for.
-            writeSessionsAsync();
+            mSettingsWriteRequest.schedule();
         }
 
         public void onSessionSealedBlocking(PackageInstallerSession session) {
             // It's very important that we block until we've recorded the
             // session as being sealed, since we never want to allow mutation
             // after sealing.
-            synchronized (mSessions) {
-                writeSessionsLocked();
-            }
+            mSettingsWriteRequest.runNow();
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 30aa8fd..51288de 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -134,6 +134,7 @@
 import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
 import static com.android.server.pm.PackageManagerServiceUtils.makeDirRecursive;
 import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
+import static com.android.server.pm.parsing.PackageInfoUtils.checkUseInstalledOrHidden;
 
 import android.Manifest;
 import android.annotation.AppIdInt;
@@ -2510,8 +2511,8 @@
             if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
 
             AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
+            PackageSetting ps = a == null ? null : mSettings.getPackageLPr(a.getPackageName());
             if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {
-                PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
                 if (ps == null) return null;
                 if (shouldFilterApplicationLocked(
                         ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
@@ -2521,8 +2522,8 @@
                         a, flags, ps.readUserState(userId), userId, ps);
             }
             if (resolveComponentName().equals(component)) {
-                return PackageParser.generateActivityInfo(
-                        mResolveActivity, flags, new PackageUserState(), userId);
+                return generateDelegateActivityInfo(pkg, ps, new PackageUserState(),
+                        mResolveActivity, flags, userId);
             }
             return null;
         }
@@ -3168,8 +3169,8 @@
                 return result;
             }
             final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
-            ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo(
-                    instantAppInstallerActivity(), 0, ps.readUserState(userId), userId);
+            ephemeralInstaller.activityInfo = generateDelegateActivityInfo(ps.getPkg(), ps,
+                    ps.readUserState(userId), instantAppInstallerActivity(), 0 /*flags*/, userId);
             ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
                     | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
             // add a non-generic filter
@@ -3253,7 +3254,7 @@
                 ai.flags = ps.pkgFlags;
                 ai.privateFlags = ps.pkgPrivateFlags;
                 pi.applicationInfo =
-                        PackageParser.generateApplicationInfo(ai, flags, state, userId);
+                        PackageInfoUtils.generateApplicationInfo(p, flags, state, userId, ps);
 
                 if (DEBUG_PACKAGE_INFO) Log.v(TAG, "ps.pkg is n/a for ["
                         + ps.name + "]. Provides a minimum info.");
@@ -3369,6 +3370,19 @@
             return getInstalledPackagesBody(flags, userId, callingUid);
         }
 
+        private static ActivityInfo generateDelegateActivityInfo(@Nullable AndroidPackage pkg,
+                @Nullable PackageSetting ps, @NonNull PackageUserState state,
+                @Nullable ActivityInfo activity, int flags, int userId) {
+            if (activity == null || pkg == null
+                    || !checkUseInstalledOrHidden(pkg, ps, state, flags)) {
+                return null;
+            }
+            final ActivityInfo info = new ActivityInfo(activity);
+            info.applicationInfo =
+                    PackageInfoUtils.generateApplicationInfo(pkg, flags, state, userId, ps);
+            return info;
+        }
+
         public ParceledListSlice<PackageInfo> getInstalledPackagesBody(int flags, int userId,
                                                                           int callingUid) {
             // writer
@@ -12711,7 +12725,8 @@
             Map<String, AndroidPackage> availablePackages)
             throws PackageManagerException {
         final ArrayList<SharedLibraryInfo> sharedLibraryInfos = collectSharedLibraryInfos(
-                pkgSetting.pkg, availablePackages, mSharedLibraries, null);
+                pkgSetting.pkg, availablePackages, mSharedLibraries, null /* newLibraries */,
+                mInjector.getCompatibility());
         executeSharedLibrariesUpdateLPr(pkg, pkgSetting, changingLib, changingLibSetting,
                 sharedLibraryInfos, mUserManager.getUserIds());
     }
@@ -12719,8 +12734,8 @@
     private static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
             Map<String, AndroidPackage> availablePackages,
             @NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
-            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
-            throws PackageManagerException {
+            @Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
+            PlatformCompat platformCompat) throws PackageManagerException {
         if (pkg == null) {
             return null;
         }
@@ -12744,9 +12759,9 @@
                     null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(),
                     usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
         }
-        // TODO(b/160928779) gate this behavior using ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES
-        if (pkg.getTargetSdkVersion() > 30) {
-            if (!pkg.getUsesNativeLibraries().isEmpty() && pkg.getTargetSdkVersion() > 30) {
+        if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
+                pkg.getPackageName(), pkg.getTargetSdkVersion())) {
+            if (!pkg.getUsesNativeLibraries().isEmpty()) {
                 usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
                         null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(),
                         usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
@@ -18875,7 +18890,7 @@
                 result.get(installPackageName).collectedSharedLibraryInfos =
                         collectSharedLibraryInfos(scanResult.request.parsedPackage,
                                 combinedPackages, request.sharedLibrarySource,
-                                incomingSharedLibraries);
+                                incomingSharedLibraries, injector.getCompatibility());
 
             } catch (PackageManagerException e) {
                 throw new ReconcileFailure(e.error, e.getMessage());
@@ -23603,7 +23618,7 @@
         boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
                 mContext.getContentResolver(),
                 android.provider.Settings.Global.COMPATIBILITY_MODE, 1) == 1;
-        PackageParser.setCompatibilityModeEnabled(compatibilityModeEnabled);
+        ParsingPackageUtils.setCompatibilityModeEnabled(compatibilityModeEnabled);
 
         if (DEBUG_SETTINGS) {
             Log.d(TAG, "compatibility mode:" + compatibilityModeEnabled);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 61f51e3..b89dbdc 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -417,7 +417,7 @@
      * Returns true if the package is installed and not hidden, or if the caller
      * explicitly wanted all uninstalled and hidden packages as well.
      */
-    private static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
+    public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
             PackageSetting pkgSetting, PackageUserState state,
             @PackageManager.PackageInfoFlags int flags) {
         // Returns false if the package is hidden system app until installed.
diff --git a/services/core/java/com/android/server/pm/utils/RequestThrottle.java b/services/core/java/com/android/server/pm/utils/RequestThrottle.java
new file mode 100644
index 0000000..f1dd402
--- /dev/null
+++ b/services/core/java/com/android/server/pm/utils/RequestThrottle.java
@@ -0,0 +1,154 @@
+/*
+ * 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.pm.utils;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+
+import com.android.server.IoThread;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Supplier;
+
+/**
+ * Loose throttle latest behavior for success/fail requests, with options to schedule or force a
+ * request through. Throttling is implicit and not configurable. This means requests are dispatched
+ * to the {@link Handler} immediately when received, and only batched while waiting on the next
+ * message execution or running request.
+ *
+ * This also means there is no explicit debouncing. Implicit debouncing is available through the
+ * same runtime delays in the {@link Handler} instance and the request execution, where multiple
+ * requests prior to the execution point are collapsed.
+ *
+ * Callers provide a {@link Handler} with which to schedule tasks on. This may be a highly
+ * contentious thread like {@link IoThread#getHandler()}, but note that there are no guarantees
+ * that the request will be handled before the system server dies. Ideally callers should handle
+ * re-initialization from stale state with no consequences to the user.
+ *
+ * This class will retry requests if they don't succeed, as provided by a true/false response from
+ * the block provided to run the request. This uses an exponential backoff mechanism, assuming that
+ * state write should be attempted immediately, but not retried so heavily as to potentially block
+ * other system server callers. Exceptions are not considered and will not result in a retry if
+ * thrown from inside the block. Caller should wrap with try-catch and rollback and transaction
+ * state before returning false to signal a retry.
+ *
+ * The caller is strictly responsible for data synchronization, as this class will not synchronize
+ * the request block, potentially running it multiple times or on multiple threads simultaneously
+ * if requests come in asynchronously.
+ */
+public class RequestThrottle {
+
+    private static final int DEFAULT_RETRY_MAX_ATTEMPTS = 5;
+    private static final int DEFAULT_DELAY_MS = 1000;
+    private static final int DEFAULT_BACKOFF_BASE = 2;
+
+    private final AtomicInteger mLastRequest = new AtomicInteger(0);
+    private final AtomicInteger mLastCommitted = new AtomicInteger(-1);
+
+    private final int mMaxAttempts;
+    private final int mFirstDelay;
+    private final int mBackoffBase;
+
+    private final AtomicInteger mCurrentRetry = new AtomicInteger(0);
+
+    @NonNull
+    private final Handler mHandler;
+
+    @NonNull
+    private final Supplier<Boolean> mBlock;
+
+    @NonNull
+    private final Runnable mRunnable;
+
+    /**
+     * @see #RequestThrottle(Handler, int, int, int, Supplier)
+     */
+    public RequestThrottle(@NonNull Handler handler, @NonNull Supplier<Boolean> block) {
+        this(handler, DEFAULT_RETRY_MAX_ATTEMPTS, DEFAULT_DELAY_MS, DEFAULT_BACKOFF_BASE,
+                block);
+    }
+
+    /**
+     * Backoff timing is calculated as firstDelay * (backoffBase ^ retryAttempt).
+     *
+     * @param handler     Representing the thread to run the provided block.
+     * @param block       The action to run when scheduled, returning whether or not the request was
+     *                    successful. Note that any thrown exceptions will be ignored and not
+     *                    retried, since it's not easy to tell how destructive or retry-able an
+     *                    exception is.
+     * @param maxAttempts Number of times to re-attempt any single request.
+     * @param firstDelay  The first delay used after the initial attempt.
+     * @param backoffBase The base of the backoff calculation, where retry attempt count is the
+     *                    exponent.
+     */
+    public RequestThrottle(@NonNull Handler handler, int maxAttempts, int firstDelay,
+            int backoffBase, @NonNull Supplier<Boolean> block) {
+        mHandler = handler;
+        mBlock = block;
+        mMaxAttempts = maxAttempts;
+        mFirstDelay = firstDelay;
+        mBackoffBase = backoffBase;
+        mRunnable = this::runInternal;
+    }
+
+    /**
+     * Schedule the intended action on the provided {@link Handler}.
+     */
+    public void schedule() {
+        // To avoid locking the Handler twice by pre-checking hasCallbacks, instead just queue
+        // the Runnable again. It will no-op if the request has already been written to disk.
+        mLastRequest.incrementAndGet();
+        mHandler.post(mRunnable);
+    }
+
+    /**
+     * Run the intended action immediately on the calling thread. Note that synchronization and
+     * deadlock between threads is not handled. This will immediately call the request block, and
+     * also potentially schedule a retry. The caller must not block itself.
+     *
+     * @return true if the write succeeded or the last request was already written
+     */
+    public boolean runNow() {
+        mLastRequest.incrementAndGet();
+        return runInternal();
+    }
+
+    private boolean runInternal() {
+        int lastRequest = mLastRequest.get();
+        int lastCommitted = mLastCommitted.get();
+        if (lastRequest == lastCommitted) {
+            return true;
+        }
+
+        if (mBlock.get()) {
+            mCurrentRetry.set(0);
+            mLastCommitted.set(lastRequest);
+            return true;
+        } else {
+            int currentRetry = mCurrentRetry.getAndIncrement();
+            if (currentRetry < mMaxAttempts) {
+                long nextDelay =
+                        (long) (mFirstDelay * Math.pow(mBackoffBase, currentRetry));
+                mHandler.postDelayed(mRunnable, nextDelay);
+            } else {
+                mCurrentRetry.set(0);
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/policy/DisplayFoldController.java b/services/core/java/com/android/server/policy/DisplayFoldController.java
index 0e12584..3c9b106 100644
--- a/services/core/java/com/android/server/policy/DisplayFoldController.java
+++ b/services/core/java/com/android/server/policy/DisplayFoldController.java
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.hardware.ICameraService;
 import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateManager.FoldStateListener;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -75,7 +76,7 @@
 
         DeviceStateManager deviceStateManager = context.getSystemService(DeviceStateManager.class);
         deviceStateManager.registerCallback(new HandlerExecutor(handler),
-                new DeviceStateListener(context));
+                new FoldStateListener(context, folded -> setDeviceFolded(folded)));
     }
 
     void finishedGoingToSleep() {
@@ -202,30 +203,4 @@
         return new DisplayFoldController(context, windowManagerService, displayService,
                 cameraServiceProxy, displayId, foldedArea, DisplayThread.getHandler());
     }
-
-    /**
-     * Listens to changes in device state and reports the state as folded if the device state
-     * matches the value in the {@link com.android.internal.R.integer.config_foldedDeviceState}
-     * resource.
-     */
-    private class DeviceStateListener implements DeviceStateManager.DeviceStateCallback {
-        private final int[] mFoldedDeviceStates;
-
-        DeviceStateListener(Context context) {
-            mFoldedDeviceStates = context.getResources().getIntArray(
-                    com.android.internal.R.array.config_foldedDeviceStates);
-        }
-
-        @Override
-        public void onStateChanged(int deviceState) {
-            boolean folded = false;
-            for (int i = 0; i < mFoldedDeviceStates.length; i++) {
-                if (deviceState == mFoldedDeviceStates[i]) {
-                    folded = true;
-                    break;
-                }
-            }
-            setDeviceFolded(folded);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7f325f1..27f5350 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3014,7 +3014,7 @@
     private int handleStartTransitionForKeyguardLw(boolean keyguardGoingAway, long duration) {
         final int res = applyKeyguardOcclusionChange();
         if (res != 0) return res;
-        if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation && keyguardGoingAway) {
+        if (!WindowManagerService.sEnableRemoteKeyguardAnimation && keyguardGoingAway) {
             if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation");
             startKeyguardExitAnimation(SystemClock.uptimeMillis(), duration);
         }
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 6e478ee7..44f14b4 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -263,8 +263,7 @@
      */
     @Deprecated
     public void setOccluded(boolean isOccluded, boolean animate) {
-        if (!WindowManagerService.sEnableRemoteKeyguardOccludeAnimation
-                && mKeyguardService != null) {
+        if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
             if (DEBUG) Log.v(TAG, "setOccluded(" + isOccluded + ") animate=" + animate);
             mKeyguardService.setOccluded(isOccluded, animate);
         }
@@ -404,8 +403,7 @@
     }
 
     public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
-        if (!WindowManagerService.sEnableRemoteKeyguardGoingAwayAnimation
-                && mKeyguardService != null) {
+        if (!WindowManagerService.sEnableRemoteKeyguardAnimation && mKeyguardService != null) {
             mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
         }
     }
diff --git a/services/core/java/com/android/server/telecom/TelecomLoaderService.java b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
index f0c96e1..32ad702 100644
--- a/services/core/java/com/android/server/telecom/TelecomLoaderService.java
+++ b/services/core/java/com/android/server/telecom/TelecomLoaderService.java
@@ -31,6 +31,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
 import android.util.IntArray;
 import android.util.Slog;
 
@@ -44,6 +45,9 @@
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Starts the telecom component by binding to its ITelecomService implementation. Telecom is setup
  * to run in the system-server process so once it is loaded into memory it will stay running.
@@ -208,13 +212,23 @@
                     return null;
                 }
             }
+            SubscriptionManager subscriptionManager =
+                    mContext.getSystemService(SubscriptionManager.class);
+            if (subscriptionManager == null) {
+                return null;
+            }
             TelecomManager telecomManager =
                     (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
-            PhoneAccountHandle phoneAccount = telecomManager.getSimCallManager(userId);
-            if (phoneAccount != null) {
-                return new String[]{phoneAccount.getComponentName().getPackageName()};
+            List<String> packages = new ArrayList<>();
+            int[] subIds = subscriptionManager.getActiveSubscriptionIdList();
+            for (int subId : subIds) {
+                PhoneAccountHandle phoneAccount =
+                        telecomManager.getSimCallManagerForSubscription(subId);
+                if (phoneAccount != null) {
+                    packages.add(phoneAccount.getComponentName().getPackageName());
+                }
             }
-            return null;
+            return packages.toArray(new String[] {});
         });
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6a1b106..f568b66 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -91,6 +91,7 @@
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
 import static android.content.res.Configuration.EMPTY;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -6858,6 +6859,11 @@
 
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfiguration) {
+        final Configuration requestedOverrideConfig = getRequestedOverrideConfiguration();
+        if (requestedOverrideConfig.assetsSeq != ASSETS_SEQ_UNDEFINED
+                && newParentConfiguration.assetsSeq > requestedOverrideConfig.assetsSeq) {
+            requestedOverrideConfig.assetsSeq = ASSETS_SEQ_UNDEFINED;
+        }
         super.resolveOverrideConfiguration(newParentConfiguration);
         final Configuration resolvedConfig = getResolvedOverrideConfiguration();
         if (isFixedRotationTransforming()) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index e249e67..c6a66c5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -472,6 +472,10 @@
 
     /** Current sequencing integer of the configuration, for skipping old configurations. */
     private int mConfigurationSeq;
+
+    /** Current sequencing integer of the asset changes, for skipping old resources overlays. */
+    private int mGlobalAssetsSeq;
+
     // To cache the list of supported system locales
     private String[] mSupportedSystemLocales = null;
 
@@ -4129,6 +4133,35 @@
         return changes;
     }
 
+    private int increaseAssetConfigurationSeq() {
+        mGlobalAssetsSeq = Math.max(++mGlobalAssetsSeq, 1);
+        return mGlobalAssetsSeq;
+    }
+
+    /**
+     * Update the asset configuration and increase the assets sequence number.
+     * @param processes the processes that needs to update the asset configuration, if none
+     *                  updates the global configuration for all processes.
+     */
+    public void updateAssetConfiguration(List<WindowProcessController> processes) {
+        synchronized (mGlobalLock) {
+            final int assetSeq = increaseAssetConfigurationSeq();
+
+            // Update the global configuration if the no target processes
+            if (processes == null) {
+                Configuration newConfig = new Configuration();
+                newConfig.assetsSeq = assetSeq;
+                updateConfiguration(newConfig);
+                return;
+            }
+
+            for (int i = processes.size() - 1; i >= 0; i--) {
+                final WindowProcessController wpc = processes.get(i);
+                wpc.updateAssetConfiguration(assetSeq);
+            }
+        }
+    }
+
     void startLaunchPowerMode(@PowerModeReason int reason) {
         if (mPowerManagerInternal == null) return;
         mPowerManagerInternal.setPowerMode(Mode.LAUNCH, true);
diff --git a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
index 62e4a85..87670d2 100644
--- a/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DragAndDropPermissionsHandler.java
@@ -32,8 +32,7 @@
 
 import java.util.ArrayList;
 
-class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub
-        implements IBinder.DeathRecipient {
+class DragAndDropPermissionsHandler extends IDragAndDropPermissions.Stub {
 
     private static final String TAG = "DragAndDrop";
     private static final boolean DEBUG = false;
@@ -49,7 +48,6 @@
 
     private IBinder mActivityToken = null;
     private IBinder mPermissionOwnerToken = null;
-    private IBinder mAppToken = null;
 
     DragAndDropPermissionsHandler(WindowManagerGlobalLock lock, ClipData clipData, int sourceUid,
             String targetPackage, int mode, int sourceUserId, int targetUserId) {
@@ -94,18 +92,15 @@
     }
 
     @Override
-    public void takeTransient(IBinder appToken) throws RemoteException {
+    public void takeTransient() throws RemoteException {
         if (mActivityToken != null || mPermissionOwnerToken != null) {
             return;
         }
         if (DEBUG) {
-            Log.d(TAG, this + ": taking permissions bound to app process: "
-                    + toHexString(appToken.hashCode()));
+            Log.d(TAG, this + ": taking transient permissions");
         }
         mPermissionOwnerToken = LocalServices.getService(UriGrantsManagerInternal.class)
                 .newUriPermissionOwner("drop");
-        mAppToken = appToken;
-        mAppToken.linkToDeath(this, 0);
 
         doTake(mPermissionOwnerToken);
     }
@@ -132,10 +127,8 @@
         } else {
             permissionOwner = mPermissionOwnerToken;
             mPermissionOwnerToken = null;
-            mAppToken.unlinkToDeath(this, 0);
-            mAppToken = null;
             if (DEBUG) {
-                Log.d(TAG, this + ": releasing process-bound permissions");
+                Log.d(TAG, this + ": releasing transient permissions");
             }
         }
 
@@ -157,15 +150,18 @@
         }
     }
 
+    /**
+     * If permissions are not tied to an activity, release whenever there are no more references
+     * to this object (if not already released).
+     */
     @Override
-    public void binderDied() {
+    protected void finalize() throws Throwable {
         if (DEBUG) {
-            Log.d(TAG, this + ": app process died: " + toHexString(mAppToken.hashCode()));
+            Log.d(TAG, this + ": running finalizer");
         }
-        try {
-            release();
-        } catch (RemoteException e) {
-            // Cannot happen, local call.
+        if (mActivityToken != null || mPermissionOwnerToken == null) {
+            return;
         }
+        release();
     }
 }
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index fb66c04..d67a0d3 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -163,14 +163,8 @@
             originalWidth = displayInfo.logicalWidth;
             originalHeight = displayInfo.logicalHeight;
         }
-        if (realOriginalRotation == Surface.ROTATION_90
-                || realOriginalRotation == Surface.ROTATION_270) {
-            mWidth = originalHeight;
-            mHeight = originalWidth;
-        } else {
-            mWidth = originalWidth;
-            mHeight = originalHeight;
-        }
+        mWidth = originalWidth;
+        mHeight = originalHeight;
 
         mOriginalRotation = originalRotation;
         // If the delta is not zero, the rotation of display may not change, but we still want to
@@ -189,8 +183,14 @@
         final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
 
         try {
+            SurfaceControl.LayerCaptureArgs args =
+                    new SurfaceControl.LayerCaptureArgs.Builder(displayContent.getSurfaceControl())
+                            .setCaptureSecureLayers(true)
+                            .setAllowProtected(true)
+                            .setSourceCrop(new Rect(0, 0, mWidth, mHeight))
+                            .build();
             SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
-                    mService.mDisplayManagerInternal.systemScreenshot(displayId);
+                    SurfaceControl.captureLayers(args);
             if (screenshotBuffer == null) {
                 Slog.w(TAG, "Unable to take screenshot of display " + displayId);
                 return;
@@ -236,9 +236,6 @@
 
             GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
                     screenshotBuffer.getHardwareBuffer());
-            // Scale the layer to the display size.
-            float dsdx = (float) mWidth / hardwareBuffer.getWidth();
-            float dsdy = (float) mHeight / hardwareBuffer.getHeight();
 
             t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
             t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
@@ -247,7 +244,6 @@
             t.setAlpha(mBackColorSurface, 1);
             t.setBuffer(mScreenshotLayer, buffer);
             t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
-            t.setMatrix(mScreenshotLayer, dsdx, 0, 0, dsdy);
             t.show(mScreenshotLayer);
             t.show(mBackColorSurface);
 
@@ -330,9 +326,8 @@
         // Compute the transformation matrix that must be applied
         // to the snapshot to make it stay in the same original position
         // with the current screen rotation.
-        int delta = deltaRotation(rotation, Surface.ROTATION_0);
+        int delta = deltaRotation(rotation, mOriginalRotation);
         RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
-
         setRotationTransform(t, mSnapshotInitialMatrix);
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7c95edd..2c592d0 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2258,7 +2258,6 @@
         mTmpPrevBounds.set(getBounds());
         final boolean wasInMultiWindowMode = inMultiWindowMode();
         final boolean wasInPictureInPicture = inPinnedWindowingMode();
-        final int oldOrientation = getOrientation();
         super.onConfigurationChanged(newParentConfig);
         // Only need to update surface size here since the super method will handle updating
         // surface position.
@@ -2301,11 +2300,6 @@
             mForceNotOrganized = false;
         }
 
-        // Report orientation change such as changing from freeform to fullscreen.
-        if (oldOrientation != getOrientation()) {
-            onDescendantOrientationChanged(this);
-        }
-
         saveLaunchingStateIfNeeded();
         final boolean taskOrgChanged = updateTaskOrganizerState(false /* forceUpdate */);
         if (taskOrgChanged) {
@@ -4315,11 +4309,21 @@
                 // the screen are opaque.
                 return TASK_VISIBILITY_INVISIBLE;
             }
-            if (isAssistantType && gotRootSplitScreenTask) {
-                // Assistant stack can't be visible behind split-screen. In addition to this not
-                // making sense, it also works around an issue here we boost the z-order of the
-                // assistant window surfaces in window manager whenever it is visible.
-                return TASK_VISIBILITY_INVISIBLE;
+            if (gotRootSplitScreenTask) {
+                if (isAssistantType) {
+                    // Assistant stack can't be visible behind split-screen. In addition to this not
+                    // making sense, it also works around an issue here we boost the z-order of the
+                    // assistant window surfaces in window manager whenever it is visible.
+                    return TASK_VISIBILITY_INVISIBLE;
+                }
+                if (other.isHomeOrRecentsRootTask()) {
+                    // While in split mode, home task will be reparented to the secondary split and
+                    // leaving tasks not supporting split below. Due to
+                    // TaskDisplayArea#assignRootTaskOrdering always adjusts home surface layer to
+                    // the bottom, this makes sure those tasks below home is invisible and won't
+                    // occlude home task unexpectedly.
+                    return TASK_VISIBILITY_INVISIBLE;
+                }
             }
             if (other.mAdjacentTask != null) {
                 if (adjacentTasks.contains(other.mAdjacentTask)) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index fbeb968..1657a13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -421,41 +421,18 @@
             SystemProperties.getBoolean(DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY, true);
 
     /**
-     * Use WMShell for app transition.
-     */
-    public static final String ENABLE_SHELL_TRANSITIONS = "persist.debug.shell_transit";
-
-    /**
-     * @see #ENABLE_SHELL_TRANSITIONS
-     */
-    public static final boolean sEnableShellTransitions =
-            SystemProperties.getBoolean(ENABLE_SHELL_TRANSITIONS, false);
-
-    /**
      * Run Keyguard animation as remote animation in System UI instead of local animation in
      * the server process.
-     *
-     * 0: Runs all keyguard animation as local animation
-     * 1: Only runs keyguard going away animation as remote animation
-     * 2: Runs all keyguard animation as remote animation
      */
     private static final String ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY =
             "persist.wm.enable_remote_keyguard_animation";
 
-    private static final int sEnableRemoteKeyguardAnimation =
-            SystemProperties.getInt(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, 1);
-
     /**
      * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
      */
-    public static final boolean sEnableRemoteKeyguardGoingAwayAnimation = !sEnableShellTransitions
-            && sEnableRemoteKeyguardAnimation >= 1;
+    public static boolean sEnableRemoteKeyguardAnimation =
+            SystemProperties.getBoolean(ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY, false);
 
-    /**
-     * @see #ENABLE_REMOTE_KEYGUARD_ANIMATION_PROPERTY
-     */
-    public static final boolean sEnableRemoteKeyguardOccludeAnimation = !sEnableShellTransitions
-            && sEnableRemoteKeyguardAnimation >= 2;
 
     /**
      * Allows a fullscreen windowing mode activity to launch in its desired orientation directly
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 4dc6007..d5965494 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -130,6 +130,12 @@
                     return runResetLetterboxStyle(pw);
                 case "set-sandbox-display-apis":
                     return runSandboxDisplayApis(pw);
+                case "set-multi-window-config":
+                    return runSetMultiWindowConfig();
+                case "get-multi-window-config":
+                    return runGetMultiWindowConfig(pw);
+                case "reset-multi-window-config":
+                    return runResetMultiWindowConfig();
                 case "reset":
                     return runReset(pw);
                 case "disable-blur":
@@ -815,6 +821,80 @@
         return 0;
     }
 
+    private int runSetMultiWindowConfig() {
+        if (peekNextArg() == null) {
+            getErrPrintWriter().println("Error: No arguments provided.");
+        }
+        int result = 0;
+        while (peekNextArg() != null) {
+            String arg = getNextArg();
+            switch (arg) {
+                case "--supportsNonResizable":
+                    result += runSetSupportsNonResizableMultiWindow();
+                    break;
+                case "--respectsActivityMinWidthHeight":
+                    result += runSetRespectsActivityMinWidthHeightMultiWindow();
+                    break;
+                default:
+                    getErrPrintWriter().println(
+                            "Error: Unrecognized multi window option: " + arg);
+                    return -1;
+            }
+        }
+        return result == 0 ? 0 : -1;
+    }
+
+    private int runSetSupportsNonResizableMultiWindow() {
+        final String arg = getNextArg();
+        if (!arg.equals("-1") && !arg.equals("0") && !arg.equals("1")) {
+            getErrPrintWriter().println("Error: a config value of [-1, 0, 1] must be provided as"
+                    + " an argument for supportsNonResizableMultiWindow");
+            return -1;
+        }
+        final int configValue = Integer.parseInt(arg);
+        synchronized (mInternal.mAtmService.mGlobalLock) {
+            mInternal.mAtmService.mSupportsNonResizableMultiWindow = configValue;
+        }
+        return 0;
+    }
+
+    private int runSetRespectsActivityMinWidthHeightMultiWindow() {
+        final String arg = getNextArg();
+        if (!arg.equals("-1") && !arg.equals("0") && !arg.equals("1")) {
+            getErrPrintWriter().println("Error: a config value of [-1, 0, 1] must be provided as"
+                    + " an argument for respectsActivityMinWidthHeightMultiWindow");
+            return -1;
+        }
+        final int configValue = Integer.parseInt(arg);
+        synchronized (mInternal.mAtmService.mGlobalLock) {
+            mInternal.mAtmService.mRespectsActivityMinWidthHeightMultiWindow = configValue;
+        }
+        return 0;
+    }
+
+    private int runGetMultiWindowConfig(PrintWriter pw) {
+        synchronized (mInternal.mAtmService.mGlobalLock) {
+            pw.println("Supports non-resizable in multi window: "
+                    + mInternal.mAtmService.mSupportsNonResizableMultiWindow);
+            pw.println("Respects activity min width/height in multi window: "
+                    + mInternal.mAtmService.mRespectsActivityMinWidthHeightMultiWindow);
+        }
+        return 0;
+    }
+
+    private int runResetMultiWindowConfig() {
+        final int supportsNonResizable = mInternal.mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_supportsNonResizableMultiWindow);
+        final int respectsActivityMinWidthHeight = mInternal.mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_respectsActivityMinWidthHeightMultiWindow);
+        synchronized (mInternal.mAtmService.mGlobalLock) {
+            mInternal.mAtmService.mSupportsNonResizableMultiWindow = supportsNonResizable;
+            mInternal.mAtmService.mRespectsActivityMinWidthHeightMultiWindow =
+                    respectsActivityMinWidthHeight;
+        }
+        return 0;
+    }
+
     private void resetLetterboxStyle() {
         synchronized (mInternal.mGlobalLock) {
             mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
@@ -879,6 +959,9 @@
         // set-sandbox-display-apis
         mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
 
+        // set-multi-window-config
+        runResetMultiWindowConfig();
+
         pw.println("Reset all settings for displayId=" + displayId);
         return 0;
     }
@@ -916,6 +999,7 @@
         pw.println("    Size Compat Mode.");
 
         printLetterboxHelp(pw);
+        printMultiWindowConfigHelp(pw);
 
         pw.println("  reset [-d DISPLAY_ID]");
         pw.println("    Reset all override settings.");
@@ -969,4 +1053,31 @@
         pw.println("  get-letterbox-style");
         pw.println("    Prints letterbox style configuration.");
     }
+
+    private void printMultiWindowConfigHelp(PrintWriter pw) {
+        pw.println("  set-multi-window-config");
+        pw.println("    Sets options to determine if activity should be shown in multi window:");
+        pw.println("      --supportsNonResizable [configValue]");
+        pw.println("        Whether the device supports non-resizable activity in multi window.");
+        pw.println("        -1: The device doesn't support non-resizable in multi window.");
+        pw.println("         0: The device supports non-resizable in multi window only if");
+        pw.println("            this is a large screen device.");
+        pw.println("         1: The device always supports non-resizable in multi window.");
+        pw.println("      --respectsActivityMinWidthHeight [configValue]");
+        pw.println("        Whether the device checks the activity min width/height to determine ");
+        pw.println("        if it can be shown in multi window.");
+        pw.println("        -1: The device ignores the activity min width/height when determining");
+        pw.println("            if it can be shown in multi window.");
+        pw.println("         0: If this is a small screen, the device compares the activity min");
+        pw.println("            width/height with the min multi window modes dimensions");
+        pw.println("            the device supports to determine if the activity can be shown in");
+        pw.println("            multi window.");
+        pw.println("         1: The device always compare the activity min width/height with the");
+        pw.println("            min multi window dimensions the device supports to determine if");
+        pw.println("            the activity can be shown in multi window.");
+        pw.println("  get-multi-window-config");
+        pw.println("    Prints values of the multi window config options.");
+        pw.println("  reset-multi-window-config");
+        pw.println("    Resets overrides to default values of the multi window config options.");
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index bac1ab1..26cfbdf 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -18,6 +18,7 @@
 
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
 import static android.os.Build.VERSION_CODES.Q;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 
@@ -1318,6 +1319,11 @@
 
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
+        final Configuration requestedOverrideConfig = getRequestedOverrideConfiguration();
+        if (requestedOverrideConfig.assetsSeq != ASSETS_SEQ_UNDEFINED
+                && newParentConfig.assetsSeq > requestedOverrideConfig.assetsSeq) {
+            requestedOverrideConfig.assetsSeq = ASSETS_SEQ_UNDEFINED;
+        }
         super.resolveOverrideConfiguration(newParentConfig);
         final Configuration resolvedConfig = getResolvedOverrideConfiguration();
         // Make sure that we don't accidentally override the activity type.
@@ -1396,6 +1402,28 @@
         return mHasPendingConfigurationChange;
     }
 
+    void updateAssetConfiguration(int assetSeq) {
+        // Update the process override configuration directly if the process configuration will
+        // not be override from its activities.
+        if (!mHasActivities || !mIsActivityConfigOverrideAllowed) {
+            Configuration overrideConfig = new Configuration(getRequestedOverrideConfiguration());
+            overrideConfig.assetsSeq = assetSeq;
+            onRequestedOverrideConfigurationChanged(overrideConfig);
+            return;
+        }
+
+        // Otherwise, we can just update the activity override configuration.
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            ActivityRecord r = mActivities.get(i);
+            Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
+            overrideConfig.assetsSeq = assetSeq;
+            r.onRequestedOverrideConfigurationChanged(overrideConfig);
+            if (r.mVisibleRequested) {
+                r.ensureActivityConfiguration(0, true);
+            }
+        }
+    }
+
     /**
      * This is called for sending {@link android.app.servertransaction.LaunchActivityItem}.
      * The caller must call {@link #setLastReportedConfiguration} if the delivered configuration
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index f3e7d67..36c0a67 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -269,7 +269,10 @@
 
 template <class Func>
 static auto makeCleanup(Func&& f) requires(!std::is_lvalue_reference_v<Func>) {
-    auto deleter = [f = std::move(f)](auto) { f(); };
+    // ok to move a 'forwarding' reference here as lvalues are disabled anyway
+    auto deleter = [f = std::move(f)](auto) { // NOLINT
+        f();
+    };
     // &f is a dangling pointer here, but we actually never use it as deleter moves it in.
     return std::unique_ptr<Func, decltype(deleter)>(&f, std::move(deleter));
 }
@@ -395,8 +398,18 @@
     return std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
 }
 
+static uint64_t elapsedUsSinceMonoTs(uint64_t monoTsUs) {
+    timespec now;
+    if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
+        return 0;
+    }
+    uint64_t nowUs = now.tv_sec * 1000000LL + now.tv_nsec / 1000;
+    return nowUs - monoTsUs;
+}
+
 void IncrementalService::onDump(int fd) {
     dprintf(fd, "Incremental is %s\n", incfs::enabled() ? "ENABLED" : "DISABLED");
+    dprintf(fd, "IncFs features: 0x%x\n", int(mIncFs->features()));
     dprintf(fd, "Incremental dir: %s\n", mIncrementalDir.c_str());
 
     std::unique_lock l(mLock);
@@ -411,6 +424,8 @@
         } else {
             dprintf(fd, "    mountId: %d\n", mnt.mountId);
             dprintf(fd, "    root: %s\n", mnt.root.c_str());
+            const auto metricsInstanceName = path::basename(ifs->root);
+            dprintf(fd, "    metrics instance name: %s\n", path::c_str(metricsInstanceName).get());
             dprintf(fd, "    nextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
             dprintf(fd, "    flags: %d\n", int(mnt.flags));
             if (mnt.startLoadingTs.time_since_epoch() == Clock::duration::zero()) {
@@ -440,6 +455,45 @@
                 dprintf(fd, "        kind: %s\n", toString(bind.kind));
             }
             dprintf(fd, "    }\n");
+
+            dprintf(fd, "    incfsMetrics: {\n");
+            const auto incfsMetrics = mIncFs->getMetrics(metricsInstanceName);
+            if (incfsMetrics) {
+                dprintf(fd, "      readsDelayedMin: %d\n", incfsMetrics.value().readsDelayedMin);
+                dprintf(fd, "      readsDelayedMinUs: %lld\n",
+                        (long long)incfsMetrics.value().readsDelayedMinUs);
+                dprintf(fd, "      readsDelayedPending: %d\n",
+                        incfsMetrics.value().readsDelayedPending);
+                dprintf(fd, "      readsDelayedPendingUs: %lld\n",
+                        (long long)incfsMetrics.value().readsDelayedPendingUs);
+                dprintf(fd, "      readsFailedHashVerification: %d\n",
+                        incfsMetrics.value().readsFailedHashVerification);
+                dprintf(fd, "      readsFailedOther: %d\n", incfsMetrics.value().readsFailedOther);
+                dprintf(fd, "      readsFailedTimedOut: %d\n",
+                        incfsMetrics.value().readsFailedTimedOut);
+            } else {
+                dprintf(fd, "      Metrics not available. Errno: %d\n", errno);
+            }
+            dprintf(fd, "    }\n");
+
+            const auto lastReadError = mIncFs->getLastReadError(ifs->control);
+            const auto errorNo = errno;
+            dprintf(fd, "    lastReadError: {\n");
+            if (lastReadError) {
+                if (lastReadError->timestampUs == 0) {
+                    dprintf(fd, "      No read errors.\n");
+                } else {
+                    dprintf(fd, "      fileId: %s\n",
+                            IncFsWrapper::toString(lastReadError->id).c_str());
+                    dprintf(fd, "      time: %llu microseconds ago\n",
+                            (unsigned long long)elapsedUsSinceMonoTs(lastReadError->timestampUs));
+                    dprintf(fd, "      blockIndex: %d\n", lastReadError->block);
+                    dprintf(fd, "      errno: %d\n", lastReadError->errorNo);
+                }
+            } else {
+                dprintf(fd, "      Info not available. Errno: %d\n", errorNo);
+            }
+            dprintf(fd, "    }\n");
         }
         dprintf(fd, "  }\n");
     }
@@ -578,7 +632,7 @@
         if (!mkdirOrLog(path::join(backing, ".incomplete"), 0777)) {
             return kInvalidStorageId;
         }
-        auto status = mVold->mountIncFs(backing, mountTarget, 0, &controlParcel);
+        auto status = mVold->mountIncFs(backing, mountTarget, 0, mountKey, &controlParcel);
         if (!status.isOk()) {
             LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
             return kInvalidStorageId;
@@ -1586,9 +1640,10 @@
 bool IncrementalService::mountExistingImage(std::string_view root) {
     auto mountTarget = path::join(root, constants().mount);
     const auto backing = path::join(root, constants().backing);
+    std::string mountKey(path::basename(path::dirname(mountTarget)));
 
     IncrementalFileSystemControlParcel controlParcel;
-    auto status = mVold->mountIncFs(backing, mountTarget, 0, &controlParcel);
+    auto status = mVold->mountIncFs(backing, mountTarget, 0, mountKey, &controlParcel);
     if (!status.isOk()) {
         LOG(ERROR) << "Vold::mountIncFs() failed: " << status.toString8();
         return false;
@@ -2562,7 +2617,9 @@
                      maxBindDelayMs)
                     .count();
     const auto bindDelayJitterRangeMs = bindDelayMs / Constants::bindDelayJitterDivider;
-    const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - bindDelayJitterRangeMs;
+    // rand() is enough, not worth maintaining a full-blown <rand> object for delay jitter
+    const auto bindDelayJitterMs = rand() % (bindDelayJitterRangeMs * 2) - // NOLINT
+            bindDelayJitterRangeMs;
     mPreviousBindDelay = std::chrono::milliseconds(bindDelayMs + bindDelayJitterMs);
     return mPreviousBindDelay;
 }
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 0755a22..68a28b2 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -43,8 +43,9 @@
     ~RealVoldService() = default;
     binder::Status mountIncFs(
             const std::string& backingPath, const std::string& targetDir, int32_t flags,
+            const std::string& sysfsName,
             os::incremental::IncrementalFileSystemControlParcel* _aidl_return) const final {
-        return mInterface->mountIncFs(backingPath, targetDir, flags, _aidl_return);
+        return mInterface->mountIncFs(backingPath, targetDir, flags, sysfsName, _aidl_return);
     }
     binder::Status unmountIncFs(const std::string& dir) const final {
         return mInterface->unmountIncFs(dir);
@@ -261,6 +262,12 @@
             return cb(control, id);
         });
     }
+    std::optional<Metrics> getMetrics(std::string_view sysfsName) const final {
+        return incfs::getMetrics(sysfsName);
+    }
+    std::optional<LastReadError> getLastReadError(const Control& control) const final {
+        return incfs::getLastReadError(control);
+    }
 };
 
 static JNIEnv* getOrAttachJniEnv(JavaVM* jvm);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 78e9589..c0ef7ba 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -51,6 +51,7 @@
     virtual ~VoldServiceWrapper() = default;
     virtual binder::Status mountIncFs(
             const std::string& backingPath, const std::string& targetDir, int32_t flags,
+            const std::string& sysfsName,
             os::incremental::IncrementalFileSystemControlParcel* result) const = 0;
     virtual binder::Status unmountIncFs(const std::string& dir) const = 0;
     virtual binder::Status bindMount(const std::string& sourceDir,
@@ -79,6 +80,8 @@
     using UniqueFd = incfs::UniqueFd;
     using WaitResult = incfs::WaitResult;
     using Features = incfs::Features;
+    using Metrics = incfs::Metrics;
+    using LastReadError = incfs::LastReadError;
 
     using ExistingMountCallback = android::base::function_ref<
             void(std::string_view root, std::string_view backingDir,
@@ -124,6 +127,8 @@
             const = 0;
     virtual ErrorCode forEachFile(const Control& control, FileCallback cb) const = 0;
     virtual ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const = 0;
+    virtual std::optional<Metrics> getMetrics(std::string_view sysfsName) const = 0;
+    virtual std::optional<LastReadError> getLastReadError(const Control& control) const = 0;
 };
 
 class AppOpsManagerWrapper {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 68586a8..da7f0db 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -49,9 +49,9 @@
 
 class MockVoldService : public VoldServiceWrapper {
 public:
-    MOCK_CONST_METHOD4(mountIncFs,
+    MOCK_CONST_METHOD5(mountIncFs,
                        binder::Status(const std::string& backingPath, const std::string& targetDir,
-                                      int32_t flags,
+                                      int32_t flags, const std::string& sysfsName,
                                       IncrementalFileSystemControlParcel* _aidl_return));
     MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir));
     MOCK_CONST_METHOD2(bindMount,
@@ -62,16 +62,16 @@
                            bool, bool));
 
     void mountIncFsFails() {
-        ON_CALL(*this, mountIncFs(_, _, _, _))
+        ON_CALL(*this, mountIncFs(_, _, _, _, _))
                 .WillByDefault(
                         Return(binder::Status::fromExceptionCode(1, String8("failed to mount"))));
     }
     void mountIncFsInvalidControlParcel() {
-        ON_CALL(*this, mountIncFs(_, _, _, _))
+        ON_CALL(*this, mountIncFs(_, _, _, _, _))
                 .WillByDefault(Invoke(this, &MockVoldService::getInvalidControlParcel));
     }
     void mountIncFsSuccess() {
-        ON_CALL(*this, mountIncFs(_, _, _, _))
+        ON_CALL(*this, mountIncFs(_, _, _, _, _))
                 .WillByDefault(Invoke(this, &MockVoldService::incFsSuccess));
     }
     void bindMountFails() {
@@ -93,12 +93,14 @@
     }
     binder::Status getInvalidControlParcel(const std::string& imagePath,
                                            const std::string& targetDir, int32_t flags,
+                                           const std::string& sysfsName,
                                            IncrementalFileSystemControlParcel* _aidl_return) {
         _aidl_return = {};
         return binder::Status::ok();
     }
     binder::Status incFsSuccess(const std::string& imagePath, const std::string& targetDir,
-                                int32_t flags, IncrementalFileSystemControlParcel* _aidl_return) {
+                                int32_t flags, const std::string& sysfsName,
+                                IncrementalFileSystemControlParcel* _aidl_return) {
         _aidl_return->pendingReads.reset(base::unique_fd(dup(STDIN_FILENO)));
         _aidl_return->cmd.reset(base::unique_fd(dup(STDIN_FILENO)));
         _aidl_return->log.reset(base::unique_fd(dup(STDIN_FILENO)));
@@ -414,6 +416,8 @@
                                  const std::vector<PerUidReadTimeouts>& perUidReadTimeouts));
     MOCK_CONST_METHOD2(forEachFile, ErrorCode(const Control& control, FileCallback cb));
     MOCK_CONST_METHOD2(forEachIncompleteFile, ErrorCode(const Control& control, FileCallback cb));
+    MOCK_CONST_METHOD1(getMetrics, std::optional<Metrics>(std::string_view path));
+    MOCK_CONST_METHOD1(getLastReadError, std::optional<LastReadError>(const Control& control));
 
     MockIncFs() {
         ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return());
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/install/RequestThrottleTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/install/RequestThrottleTest.kt
new file mode 100644
index 0000000..2196ef7
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/install/RequestThrottleTest.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.pm.test.install
+
+import com.android.server.pm.utils.RequestThrottle
+import com.android.server.testutils.TestHandler
+import com.google.common.collect.Range
+import com.google.common.truth.LongSubject
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.CyclicBarrier
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicLong
+
+class RequestThrottleTest {
+
+    private val counter = AtomicInteger(0)
+
+    private val handler = TestHandler(null)
+
+    @Before
+    fun resetValues() {
+        handler.flush()
+        counter.set(0)
+        assertThat(counter.get()).isEqualTo(0)
+    }
+
+    @Test
+    fun simpleThrottle() {
+        val request = RequestThrottle(handler) {
+            counter.incrementAndGet()
+            true
+        }
+
+        fun sendRequests() {
+            request.schedule()
+            val thread = startThread { request.schedule() }
+            request.schedule()
+            thread.joinForTest()
+        }
+
+        sendRequests()
+        handler.flush()
+        assertThat(counter.get()).isEqualTo(1)
+
+        sendRequests()
+        handler.flush()
+        assertThat(counter.get()).isEqualTo(2)
+    }
+
+    @Test
+    fun exceptionInRequest() {
+        val shouldThrow = AtomicBoolean(true)
+        val request = RequestThrottle(handler) {
+            if (shouldThrow.get()) {
+                throw RuntimeException()
+            }
+            counter.incrementAndGet()
+            true
+        }
+
+        fun sendRequests() {
+            request.schedule()
+            val thread = startThread { request.schedule() }
+            request.schedule()
+            thread.joinForTest()
+        }
+
+        sendRequests()
+        try {
+            handler.flush()
+        } catch (ignored: Exception) {
+        }
+        assertThat(counter.get()).isEqualTo(0)
+
+        shouldThrow.set(false)
+
+        sendRequests()
+        handler.flush()
+        assertThat(counter.get()).isEqualTo(1)
+    }
+
+    @Test
+    fun scheduleWhileRunning() {
+        val latchForStartRequest = CountDownLatch(1)
+        val latchForEndRequest = CountDownLatch(1)
+        val request = RequestThrottle(handler) {
+            latchForStartRequest.countDown()
+            counter.incrementAndGet()
+            latchForEndRequest.awaitForTest()
+            true
+        }
+
+        // Schedule and block a request
+        request.schedule()
+        val handlerThread = startThread { handler.timeAdvance() }
+        latchForStartRequest.awaitForTest()
+
+        // Hit it with other requests
+        request.schedule()
+        (0..5).map { startThread { request.schedule() } }
+                .forEach { it.joinForTest() }
+
+        // Release everything
+        latchForEndRequest.countDown()
+        handlerThread.join()
+        handler.flush()
+
+        // Ensure another request was run after initial blocking request ends
+        assertThat(counter.get()).isEqualTo(2)
+    }
+
+    @Test
+    fun backoffRetry() {
+        val time = AtomicLong(0)
+        val handler = TestHandler(null) { time.get() }
+        val returnValue = AtomicBoolean(false)
+        val request = RequestThrottle(handler, 3, 1000, 2) {
+            counter.incrementAndGet()
+            returnValue.get()
+        }
+
+        request.schedule()
+
+        handler.timeAdvance()
+        handler.pendingMessages.apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(single().sendTime).isAround(1000)
+        }
+
+        time.set(1000)
+        handler.timeAdvance()
+        handler.pendingMessages.apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(single().sendTime).isAround(3000)
+        }
+
+        time.set(3000)
+        handler.timeAdvance()
+        handler.pendingMessages.apply {
+            assertThat(size).isEqualTo(1)
+            assertThat(single().sendTime).isAround(7000)
+        }
+
+        returnValue.set(true)
+        time.set(7000)
+        handler.timeAdvance()
+        assertThat(handler.pendingMessages).isEmpty()
+
+        // Ensure another request was run after initial blocking request ends
+        assertThat(counter.get()).isEqualTo(4)
+    }
+
+    @Test
+    fun forceWriteMultiple() {
+        val request = RequestThrottle(handler) {
+            counter.incrementAndGet()
+            true
+        }
+
+        request.runNow()
+        request.runNow()
+        request.runNow()
+
+        assertThat(counter.get()).isEqualTo(3)
+    }
+
+    @Test
+    fun forceWriteNowWithoutSync() {
+        // When forcing a write without synchronizing the request block, 2 instances will be run.
+        // There is no test for "with sync" because any logic to avoid multiple runs is left
+        // entirely up to the caller.
+
+        val barrierForEndRequest = CyclicBarrier(2)
+        val request = RequestThrottle(handler) {
+            counter.incrementAndGet()
+            barrierForEndRequest.awaitForTest()
+            true
+        }
+
+        // Schedule and block a request
+        request.schedule()
+        val thread = startThread { handler.timeAdvance() }
+
+        request.runNow()
+
+        thread.joinForTest()
+
+        assertThat(counter.get()).isEqualTo(2)
+    }
+
+    private fun CountDownLatch.awaitForTest() = assertThat(await(5, TimeUnit.SECONDS)).isTrue()
+    private fun CyclicBarrier.awaitForTest() = await(5, TimeUnit.SECONDS)
+    private fun Thread.joinForTest() = join(5000)
+
+    private fun startThread(block: () -> Unit) = Thread { block() }.apply { start() }
+
+    // Float math means time calculations are not exact, so use a loose range
+    private fun LongSubject.isAround(value: Long, threshold: Long = 10) =
+            isIn(Range.closed(value - threshold, value + threshold))
+}
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/OWNERS b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/OWNERS
new file mode 100644
index 0000000..c74393b
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
new file mode 100644
index 0000000..a4de08a
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file1.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="true" />
+        <individual-sensor-privacy sensor="2" enabled="true" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file2.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file2.xml
new file mode 100644
index 0000000..361075e
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file2.xml
@@ -0,0 +1,16 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="true" />
+        <individual-sensor-privacy sensor="2" enabled="true" />
+    </user>
+    <user id="10" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="true" />
+        <individual-sensor-privacy sensor="2" enabled="false" />
+    </user>
+    <user id="11" enabled="false">
+    </user>
+    <user id="12" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="true" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file3.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file3.xml
new file mode 100644
index 0000000..e8f9edf
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file3.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="false" />
+        <individual-sensor-privacy sensor="2" enabled="false" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file4.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file4.xml
new file mode 100644
index 0000000..d26c275
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file4.xml
@@ -0,0 +1,10 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="true" />
+        <individual-sensor-privacy sensor="2" enabled="false" />
+    </user>
+    <user id="10" enabled="false">
+        <individual-sensor-privacy sensor="1" enabled="false" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file5.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file5.xml
new file mode 100644
index 0000000..5c9d0cd
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file5.xml
@@ -0,0 +1,9 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+    <user id="0" enabled="false">
+        <individual-sensor-privacy sensor="2" enabled="false" />
+    </user>
+    <user id="10" enabled="false">
+        <individual-sensor-privacy sensor="2" enabled="false" />
+    </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 24b85f0..92e4ec9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -61,6 +61,8 @@
 import android.location.LastLocationRequest;
 import android.location.Location;
 import android.location.LocationManagerInternal;
+import android.location.LocationManagerInternal.LocationTagInfo;
+import android.location.LocationManagerInternal.OnProviderLocationTagsChangeListener;
 import android.location.LocationManagerInternal.ProviderEnabledListener;
 import android.location.LocationRequest;
 import android.location.LocationResult;
@@ -90,6 +92,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 
@@ -216,6 +219,21 @@
     }
 
     @Test
+    public void testAttributionTags() {
+        OnProviderLocationTagsChangeListener listener = mock(
+                OnProviderLocationTagsChangeListener.class);
+        mManager.setOnProviderLocationTagsChangeListener(listener);
+
+        mProvider.setExtraAttributionTags(Collections.singleton("extra"));
+
+        ArgumentCaptor<LocationTagInfo> captor = ArgumentCaptor.forClass(LocationTagInfo.class);
+        verify(listener, times(2)).onLocationTagsChanged(captor.capture());
+
+        assertThat(captor.getAllValues().get(0).getTags()).isEmpty();
+        assertThat(captor.getAllValues().get(1).getTags()).containsExactly("extra", "attribution");
+    }
+
+    @Test
     public void testRemoveProvider() {
         mManager.setRealProvider(null);
         assertThat(mManager.hasProvider()).isFalse();
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/OWNERS b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/OWNERS
new file mode 100644
index 0000000..c74393b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
new file mode 100644
index 0000000..844687f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.sensorprivacy;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.Environment;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.LocalServices;
+import com.android.server.SensorPrivacyService;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+@RunWith(AndroidTestingRunner.class)
+public class SensorPrivacyServiceMockingTest {
+
+    private static final String PERSISTENCE_FILE_PATHS_TEMPLATE =
+            "SensorPrivacyServiceMockingTest/persisted_file%d.xml";
+    public static final String PERSISTENCE_FILE1 =
+            String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 1);
+    public static final String PERSISTENCE_FILE2 =
+            String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 2);
+    public static final String PERSISTENCE_FILE3 =
+            String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 3);
+    public static final String PERSISTENCE_FILE4 =
+            String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 4);
+    public static final String PERSISTENCE_FILE5 =
+            String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 5);
+
+    private Context mContext;
+    @Mock
+    private AppOpsManager mMockedAppOpsManager;
+    @Mock
+    private UserManagerInternal mMockedUserManagerInternal;
+    @Mock
+    private ActivityManager mMockedActivityManager;
+    @Mock
+    private ActivityTaskManager mMockedActivityTaskManager;
+    @Mock
+    private TelephonyManager mMockedTelephonyManager;
+
+    @Test
+    public void testServiceInit() throws IOException {
+        MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.WARN)
+                .spyStatic(LocalServices.class)
+                .spyStatic(Environment.class)
+                .startMocking();
+
+        try {
+            mContext = InstrumentationRegistry.getInstrumentation().getContext();
+            spyOn(mContext);
+
+            doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+            doReturn(mMockedUserManagerInternal)
+                    .when(() -> LocalServices.getService(UserManagerInternal.class));
+            doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
+            doReturn(mMockedActivityTaskManager)
+                    .when(mContext).getSystemService(ActivityTaskManager.class);
+            doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
+                    TelephonyManager.class);
+
+            String dataDir = mContext.getApplicationInfo().dataDir;
+            doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+
+            File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
+            onDeviceFile.delete();
+
+            // Try all files with one known user
+            doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
+            doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+                    .getUserInfo(0);
+            initServiceWithPersistenceFile(onDeviceFile, null);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
+
+            // Try all files with two known users
+            doReturn(new int[]{0, 10}).when(mMockedUserManagerInternal).getUserIds();
+            doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+                    .getUserInfo(0);
+            doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+                    .getUserInfo(10);
+            initServiceWithPersistenceFile(onDeviceFile, null);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE1);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE2);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE3);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE4);
+            initServiceWithPersistenceFile(onDeviceFile, PERSISTENCE_FILE5);
+
+        } finally {
+            mockitoSession.finishMocking();
+        }
+    }
+
+    private void initServiceWithPersistenceFile(File onDeviceFile,
+            String persistenceFilePath) throws IOException {
+        if (persistenceFilePath != null) {
+            Files.copy(mContext.getAssets().open(persistenceFilePath),
+                    onDeviceFile.toPath());
+        }
+        new SensorPrivacyService(mContext);
+        onDeviceFile.delete();
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index 638b1b4..8344049 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -30,6 +30,7 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManagerInternal;
 
 import com.android.server.LocalServices;
@@ -57,8 +58,36 @@
     private ProcessRecord mProcessRecord;
 
     private static final long ZERO = 0L;
-    private static final long USAGE_STATS_INTERACTION = 2 * 60 * 60 * 1000L;
-    private static final long SERVICE_USAGE_INTERACTION = 30 * 60 * 1000;
+    private static final long USAGE_STATS_INTERACTION = 10 * 60 * 1000L;
+    private static final long SERVICE_USAGE_INTERACTION = 60 * 1000;
+
+    static class MyOomAdjuster extends OomAdjuster {
+
+        private final PlatformCompatCache mPlatformCompatCache;
+
+        MyOomAdjuster(ActivityManagerService service, ProcessList processList,
+                ActiveUids activeUids) {
+            super(service, processList, activeUids);
+            mPlatformCompatCache = new MyPlatformCompatCache(new long[]{});
+        }
+
+        static class MyPlatformCompatCache extends PlatformCompatCache {
+
+            MyPlatformCompatCache(long[] compatChanges) {
+                super(compatChanges);
+            }
+
+            @Override
+            boolean isChangeEnabled(long changeId, ApplicationInfo app, boolean defaultValue) {
+                return true;
+            }
+        }
+
+        @Override
+        protected OomAdjuster.PlatformCompatCache getPlatformCompatCache() {
+            return mPlatformCompatCache;
+        }
+    }
 
     @BeforeClass
     public static void setUpOnce() {
@@ -84,7 +113,7 @@
             final AppProfiler profiler = mock(AppProfiler.class);
             setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
             setFieldValue(ActivityManagerService.class, sService, "mAppProfiler", profiler);
-            sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList, null);
+            sService.mOomAdjuster = new MyOomAdjuster(sService, sService.mProcessList, null);
             LocalServices.addService(UsageStatsManagerInternal.class,
                     mock(UsageStatsManagerInternal.class));
             sService.mUsageStatsService = LocalServices.getService(UsageStatsManagerInternal.class);
@@ -119,8 +148,10 @@
 
         // Ensure certain services and constants are defined properly
         assertNotNull(sService.mUsageStatsService);
-        assertEquals(USAGE_STATS_INTERACTION, sService.mConstants.USAGE_STATS_INTERACTION_INTERVAL);
-        assertEquals(SERVICE_USAGE_INTERACTION, sService.mConstants.SERVICE_USAGE_INTERACTION_TIME);
+        assertEquals(USAGE_STATS_INTERACTION,
+                sService.mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S);
+        assertEquals(SERVICE_USAGE_INTERACTION,
+                sService.mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/utils/OWNERS b/services/tests/servicestests/src/com/android/server/utils/OWNERS
new file mode 100644
index 0000000..1853220
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/utils/OWNERS
@@ -0,0 +1,4 @@
+per-file WatchableTester.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file WatchableTester.java = shombert@google.com
+per-file WatcherTest.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file WatcherTest.java = shombert@google.com
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 13ef998..9226c0b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -62,7 +62,6 @@
 import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 
 import android.app.ActivityManager;
 import android.app.TaskInfo;
@@ -1323,20 +1322,22 @@
     }
 
     @Test
-    public void testNotifyOrientationChangeCausedByConfigurationChange() {
+    public void testTaskOrientationOnDisplayWindowingModeChange() {
+        // Skip unnecessary operations to speed up the test.
+        mAtm.deferWindowLayout();
         final Task task = getTestTask();
         final ActivityRecord activity = task.getTopMostActivity();
         final DisplayContent display = task.getDisplayContent();
-        display.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        mWm.setWindowingMode(display.mDisplayId, WINDOWING_MODE_FREEFORM);
 
         activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
         assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
-        verify(display).onDescendantOrientationChanged(same(task));
-        reset(display);
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, display.getLastOrientation());
 
-        display.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mWm.setWindowingMode(display.mDisplayId, WINDOWING_MODE_FULLSCREEN);
         assertEquals(SCREEN_ORIENTATION_LANDSCAPE, task.getOrientation());
-        verify(display).onDescendantOrientationChanged(same(task));
+        assertEquals(SCREEN_ORIENTATION_LANDSCAPE, display.getLastOrientation());
+        assertEquals(Configuration.ORIENTATION_LANDSCAPE, display.getConfiguration().orientation);
     }
 
     @Test
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d77ab2b..f0d43fa 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -2543,6 +2543,7 @@
         } else if (mRttCall != null && parcelableCall.getParcelableRttCall() == null
                 && parcelableCall.getIsRttCallChanged()) {
             isRttChanged = true;
+            mRttCall.close();
             mRttCall = null;
         }
 
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 2616ec8..114c90b 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3402,7 +3402,10 @@
      * Set uicc applications being enabled or disabled.
      * The value will be remembered on the subscription and will be applied whenever it's present.
      * If the subscription in currently present, it will also apply the setting to modem
-     * immediately.
+     * immediately (the setting in the modem will not change until the modem receives and responds
+     * to the request, but typically this should only take a few seconds. The user visible setting
+     * available from SubscriptionInfo.areUiccApplicationsEnabled() will be updated
+     * immediately.)
      *
      * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
      *
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 05f5d29..78da86c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1936,11 +1936,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
      *     active subscription.
      *     <li>If the calling app is the default SMS role holder (see {@link
@@ -1989,11 +1987,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
      *     active subscription.
      *     <li>If the calling app is the default SMS role holder (see {@link
@@ -2058,11 +2054,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
      *     active subscription.
      *     <li>If the calling app is the default SMS role holder (see {@link
@@ -2138,11 +2132,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
      *     active subscription.
      *     <li>If the calling app is the default SMS role holder (see {@link
@@ -2176,11 +2168,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) on any
      *     active subscription.
      *     <li>If the calling app is the default SMS role holder (see {@link
@@ -2260,11 +2250,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *     <li>If the calling app is the default SMS role holder (see {@link
      *     RoleManager#isRoleHeld(String)}).
@@ -2297,11 +2285,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *     <li>If the calling app is the default SMS role holder (see {@link
      *     RoleManager#isRoleHeld(String)}).
@@ -3783,11 +3769,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *     <li>If the calling app is the default SMS role holder (see {@link
      *     RoleManager#isRoleHeld(String)}).
@@ -3821,11 +3805,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *     <li>If the calling app is the default SMS role holder (see {@link
      *     RoleManager#isRoleHeld(String)}).
@@ -4075,11 +4057,9 @@
      * <ul>
      *     <li>If the calling app has been granted the READ_PRIVILEGED_PHONE_STATE permission; this
      *     is a privileged permission that can only be granted to apps preloaded on the device.
-     *     <li>If the calling app is the device or profile owner and has been granted the
-     *     {@link Manifest.permission#READ_PHONE_STATE} permission. The profile owner is an app that
-     *     owns a managed profile on the device; for more details see <a
-     *     href="https://developer.android.com/work/managed-profiles">Work profiles</a>.
-     *     Profile owner access is deprecated and will be removed in a future release.
+     *     <li>If the calling app is the device owner of a fully-managed device, a profile
+     *     owner of an organization-owned device, or their delegates (see {@link
+     *     android.app.admin.DevicePolicyManager#getEnrollmentSpecificId()}).
      *     <li>If the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *     <li>If the calling app is the default SMS role holder (see {@link
      *     RoleManager#isRoleHeld(String)}).
@@ -15036,6 +15016,15 @@
             "CAPABILITY_SLICING_CONFIG_SUPPORTED";
 
     /**
+     * Indicates whether PHYSICAL_CHANNEL_CONFIG HAL1.6 is supported. See comments on
+     * respective methods for more information.
+     *
+     * @hide
+     */
+    public static final String CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED =
+            "CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED";
+
+    /**
      * A list of the radio interface capability values with public valid constants.
      *
      * Here is a related list for the systemapi-only valid constants:
diff --git a/tests/HwAccelerationTest/res/layout/stretch_layout.xml b/tests/HwAccelerationTest/res/layout/stretch_layout.xml
index df5f297..81e0c01 100644
--- a/tests/HwAccelerationTest/res/layout/stretch_layout.xml
+++ b/tests/HwAccelerationTest/res/layout/stretch_layout.xml
@@ -16,7 +16,6 @@
 <ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/scroll_view"
-    android:edgeEffectType="stretch"
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
     <LinearLayout
@@ -26,7 +25,6 @@
 
         <HorizontalScrollView
             android:id="@+id/horizontal_scroll_view"
-            android:edgeEffectType="stretch"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
             <LinearLayout
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 21386b8..f2c3b86 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -969,6 +969,8 @@
                 densities.add(dens);
             }
 
+            std::vector<ResXMLParser::ResXMLPosition> tagsToSkip;
+
             size_t len;
             ResXMLTree::event_code_t code;
             int depth = 0;
@@ -1091,6 +1093,42 @@
             Vector<FeatureGroup> featureGroups;
             KeyedVector<String8, ImpliedFeature> impliedFeatures;
 
+            {
+                int curDepth = 0;
+                ResXMLParser::ResXMLPosition initialPos;
+                tree.getPosition(&initialPos);
+
+                // Find all of the "uses-sdk" tags within the "manifest" tag.
+                std::vector<ResXMLParser::ResXMLPosition> usesSdkTagPositions;
+                ResXMLParser::ResXMLPosition curPos;
+                while ((code = tree.next()) != ResXMLTree::END_DOCUMENT &&
+                       code != ResXMLTree::BAD_DOCUMENT) {
+                    if (code == ResXMLTree::END_TAG) {
+                        curDepth--;
+                        continue;
+                    }
+                    if (code == ResXMLTree::START_TAG) {
+                        curDepth++;
+                    }
+                    const char16_t* ctag16 = tree.getElementName(&len);
+                    if (ctag16 == NULL || String8(ctag16) != "uses-sdk" || curDepth != 2) {
+                        continue;
+                    }
+
+                    tree.getPosition(&curPos);
+                    usesSdkTagPositions.emplace_back(curPos);
+                }
+
+                // Skip all "uses-sdk" tags besides the very last tag. The android runtime only uses
+                // the attribute values from the last defined tag.
+                for (size_t i = 0; i < usesSdkTagPositions.size() - 1; i++) {
+                    tagsToSkip.emplace_back(usesSdkTagPositions[i]);
+                }
+
+                // Reset the position before parsing.
+                tree.setPosition(initialPos);
+            }
+
             while ((code=tree.next()) != ResXMLTree::END_DOCUMENT &&
                     code != ResXMLTree::BAD_DOCUMENT) {
                 if (code == ResXMLTree::END_TAG) {
@@ -1202,8 +1240,25 @@
                 if (code != ResXMLTree::START_TAG) {
                     continue;
                 }
+
                 depth++;
 
+                // If this tag should be skipped, skip to the end of this tag.
+                ResXMLParser::ResXMLPosition curPos;
+                tree.getPosition(&curPos);
+                if (std::find(tagsToSkip.begin(), tagsToSkip.end(), curPos) != tagsToSkip.end()) {
+                    const int breakDepth = depth - 1;
+                    while ((code = tree.next()) != ResXMLTree::END_DOCUMENT &&
+                           code != ResXMLTree::BAD_DOCUMENT) {
+                        if (code == ResXMLTree::END_TAG && --depth == breakDepth) {
+                            break;
+                        } else if (code == ResXMLTree::START_TAG) {
+                            depth++;
+                        }
+                    }
+                    continue;
+                }
+
                 const char16_t* ctag16 = tree.getElementName(&len);
                 if (ctag16 == NULL) {
                     SourcePos(manifestFile, tree.getLineNumber()).error(