Merge "Bubbles: fix some issues with the pointer" 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 7ed8b6c..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
@@ -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/api/test-current.txt b/core/api/test-current.txt
index 35767b3..1330234 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1810,6 +1810,7 @@
 
   public class VintfObject {
     method public static String[] getHalNamesAndVersions();
+    method @NonNull public static String getPlatformSepolicyVersion();
     method public static String getSepolicyVersion();
     method public static Long getTargetFrameworkCompatibilityMatrixVersion();
     method public static java.util.Map<java.lang.String,java.lang.String[]> getVndkSnapshots();
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..75a38c2 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
     };
@@ -4723,8 +4723,8 @@
     }
 
     /**
-     * Flag for querying app op history: get only aggregate information and no
-     * discrete accesses.
+     * Flag for querying app op history: get only aggregate information (counts of events) and no
+     * discret accesses information - specific accesses with timestamp.
      *
      * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
      *
@@ -4735,8 +4735,8 @@
     public static final int HISTORY_FLAG_AGGREGATE = 1 << 0;
 
     /**
-     * Flag for querying app op history: get only discrete information and no
-     * aggregate accesses.
+     * Flag for querying app op history: get only discrete access information (only specific
+     * accesses with timestamps) and no aggregate information (counts over time).
      *
      * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
      *
@@ -4747,7 +4747,7 @@
     public static final int HISTORY_FLAG_DISCRETE = 1 << 1;
 
     /**
-     * Flag for querying app op history: get all types of historical accesses.
+     * Flag for querying app op history: get all types of historical access information.
      *
      * @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
      *
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 7dc662a..609c014 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -5513,6 +5513,10 @@
      * This method requires the caller to be the device owner.
      * <p>
      * This proxy is only a recommendation and it is possible that some apps will ignore it.
+     * <p>
+     * Note: The device owner won't be able to set a global HTTP proxy if there are unaffiliated
+     * secondary users or profiles on the device. It's recommended that affiliation ids are set for
+     * new users as soon as possible after provisioning via {@link #setAffiliationIds}.
      *
      * @see ProxyInfo
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
@@ -12791,6 +12795,11 @@
      * <p>In this mode, the DNS subsystem will attempt a TLS handshake to the network-supplied
      * resolver prior to attempting name resolution in cleartext.
      *
+     * <p>Note: The device owner won't be able to set the global private DNS mode if there are
+     * unaffiliated secondary users or profiles on the device. It's recommended that affiliation
+     * ids are set for new users as soon as possible after provisioning via
+     * {@link #setAffiliationIds}.
+     *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with.
      *
      * @return {@code PRIVATE_DNS_SET_NO_ERROR} if the mode was set successfully, or
@@ -12826,6 +12835,11 @@
      * the ability to resolve hostnames as system traffic to the resolver may not go through the
      * VPN.
      *
+     * <p>Note: The device owner won't be able to set the global private DNS mode if there are
+     * unaffiliated secondary users or profiles on the device. It's recommended that affiliation
+     * ids are set for new users as soon as possible after provisioning via
+     * {@link #setAffiliationIds}.
+     *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with.
      * @param privateDnsHost The hostname of a server that implements DNS over TLS (RFC7858).
      *
diff --git a/core/java/android/app/people/PeopleSpaceTile.java b/core/java/android/app/people/PeopleSpaceTile.java
index de3eeee..2dbbfdf 100644
--- a/core/java/android/app/people/PeopleSpaceTile.java
+++ b/core/java/android/app/people/PeopleSpaceTile.java
@@ -43,6 +43,12 @@
  */
 public class PeopleSpaceTile implements Parcelable {
 
+    public static final int SHOW_CONVERSATIONS = 1 << 0;
+    public static final int BLOCK_CONVERSATIONS =  1 << 1;
+    public static final int SHOW_IMPORTANT_CONVERSATIONS = 1 << 2;
+    public static final int SHOW_STARRED_CONTACTS = 1 << 3;
+    public static final int SHOW_CONTACTS = 1 << 4;
+
     private String mId;
     private CharSequence mUserName;
     private Icon mUserIcon;
@@ -61,6 +67,11 @@
     private Intent mIntent;
     private long mNotificationTimestamp;
     private List<ConversationStatus> mStatuses;
+    private boolean mCanBypassDnd;
+    private boolean mIsPackageSuspended;
+    private boolean mIsUserQuieted;
+    private int mNotificationPolicyState;
+    private float mContactAffinity;
 
     private PeopleSpaceTile(Builder b) {
         mId = b.mId;
@@ -81,6 +92,11 @@
         mIntent = b.mIntent;
         mNotificationTimestamp = b.mNotificationTimestamp;
         mStatuses = b.mStatuses;
+        mCanBypassDnd = b.mCanBypassDnd;
+        mIsPackageSuspended = b.mIsPackageSuspended;
+        mIsUserQuieted = b.mIsUserQuieted;
+        mNotificationPolicyState = b.mNotificationPolicyState;
+        mContactAffinity = b.mContactAffinity;
     }
 
     public String getId() {
@@ -173,6 +189,41 @@
         return mStatuses;
     }
 
+    /**
+     * Whether the app associated with the conversation can bypass DND.
+     */
+    public boolean canBypassDnd() {
+        return mCanBypassDnd;
+    }
+
+    /**
+     * Whether the app associated with the conversation is suspended.
+     */
+    public boolean isPackageSuspended() {
+        return mIsPackageSuspended;
+    }
+
+    /**
+     * Whether the user associated with the conversation is quieted.
+     */
+    public boolean isUserQuieted() {
+        return mIsUserQuieted;
+    }
+
+    /**
+     * Returns the state of notifications for the conversation.
+     */
+    public int getNotificationPolicyState() {
+        return mNotificationPolicyState;
+    }
+
+    /**
+     * Returns the contact affinity (whether the contact is starred).
+     */
+    public float getContactAffinity() {
+        return mContactAffinity;
+    }
+
     /** Converts a {@link PeopleSpaceTile} into a {@link PeopleSpaceTile.Builder}. */
     public Builder toBuilder() {
         Builder builder =
@@ -192,6 +243,11 @@
         builder.setIntent(mIntent);
         builder.setNotificationTimestamp(mNotificationTimestamp);
         builder.setStatuses(mStatuses);
+        builder.setCanBypassDnd(mCanBypassDnd);
+        builder.setIsPackageSuspended(mIsPackageSuspended);
+        builder.setIsUserQuieted(mIsUserQuieted);
+        builder.setNotificationPolicyState(mNotificationPolicyState);
+        builder.setContactAffinity(mContactAffinity);
         return builder;
     }
 
@@ -215,6 +271,11 @@
         private Intent mIntent;
         private long mNotificationTimestamp;
         private List<ConversationStatus> mStatuses;
+        private boolean mCanBypassDnd;
+        private boolean mIsPackageSuspended;
+        private boolean mIsUserQuieted;
+        private int mNotificationPolicyState;
+        private float mContactAffinity;
 
         /** Builder for use only if a shortcut is not available for the tile. */
         public Builder(String id, CharSequence userName, Icon userIcon, Intent intent) {
@@ -223,6 +284,7 @@
             mUserIcon = userIcon;
             mIntent = intent;
             mPackageName = intent == null ? null : intent.getPackage();
+            mNotificationPolicyState = SHOW_CONVERSATIONS;
         }
 
         public Builder(ShortcutInfo info, LauncherApps launcherApps) {
@@ -232,6 +294,7 @@
             mUserHandle = info.getUserHandle();
             mPackageName = info.getPackage();
             mContactUri = getContactUri(info);
+            mNotificationPolicyState = SHOW_CONVERSATIONS;
         }
 
         public Builder(ConversationChannel channel, LauncherApps launcherApps) {
@@ -246,6 +309,9 @@
             mLastInteractionTimestamp = channel.getLastEventTimestamp();
             mIsImportantConversation = channel.getParentNotificationChannel() != null
                     && channel.getParentNotificationChannel().isImportantConversation();
+            mCanBypassDnd = channel.getParentNotificationChannel() != null
+                    && channel.getParentNotificationChannel().canBypassDnd();
+            mNotificationPolicyState = SHOW_CONVERSATIONS;
         }
 
         /** Returns the Contact's Uri if present. */
@@ -366,6 +432,36 @@
             return this;
         }
 
+        /** Sets whether the conversation channel can bypass DND. */
+        public Builder setCanBypassDnd(boolean canBypassDnd) {
+            mCanBypassDnd = canBypassDnd;
+            return this;
+        }
+
+        /** Sets whether the package is suspended. */
+        public Builder setIsPackageSuspended(boolean isPackageSuspended) {
+            mIsPackageSuspended = isPackageSuspended;
+            return this;
+        }
+
+        /** Sets whether the user has been quieted. */
+        public Builder setIsUserQuieted(boolean isUserQuieted) {
+            mIsUserQuieted = isUserQuieted;
+            return this;
+        }
+
+        /** Sets the state of blocked notifications for the conversation. */
+        public Builder setNotificationPolicyState(int notificationPolicyState) {
+            mNotificationPolicyState = notificationPolicyState;
+            return this;
+        }
+
+        /** Sets the contact's affinity. */
+        public Builder setContactAffinity(float contactAffinity) {
+            mContactAffinity = contactAffinity;
+            return this;
+        }
+
         /** Builds a {@link PeopleSpaceTile}. */
         @NonNull
         public PeopleSpaceTile build() {
@@ -393,6 +489,11 @@
         mNotificationTimestamp = in.readLong();
         mStatuses = new ArrayList<>();
         in.readParcelableList(mStatuses, ConversationStatus.class.getClassLoader());
+        mCanBypassDnd = in.readBoolean();
+        mIsPackageSuspended = in.readBoolean();
+        mIsUserQuieted = in.readBoolean();
+        mNotificationPolicyState = in.readInt();
+        mContactAffinity = in.readFloat();
     }
 
     @Override
@@ -420,6 +521,11 @@
         dest.writeParcelable(mIntent, flags);
         dest.writeLong(mNotificationTimestamp);
         dest.writeParcelableList(mStatuses, flags);
+        dest.writeBoolean(mCanBypassDnd);
+        dest.writeBoolean(mIsPackageSuspended);
+        dest.writeBoolean(mIsUserQuieted);
+        dest.writeInt(mNotificationPolicyState);
+        dest.writeFloat(mContactAffinity);
     }
 
     public static final @android.annotation.NonNull
@@ -427,7 +533,6 @@
                 public PeopleSpaceTile createFromParcel(Parcel source) {
                     return new PeopleSpaceTile(source);
                 }
-
                 public PeopleSpaceTile[] newArray(int size) {
                     return new PeopleSpaceTile[size];
                 }
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/FileUtils.java b/core/java/android/os/FileUtils.java
index a06a857..f3e0ce9 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -1470,7 +1470,7 @@
             return MediaStore.getOriginalMediaFormatFileDescriptor(context,
                     ParcelFileDescriptor.dup(fd));
         } catch (Exception e) {
-            Log.w(TAG, "Failed to convert to modern format file descriptor", e);
+            Log.d(TAG, "Failed to convert to modern format file descriptor", e);
             return null;
         }
     }
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/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 7af8f71..bf0b655 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.util.Slog;
 
@@ -112,6 +113,15 @@
     public static native String getSepolicyVersion();
 
     /**
+     * @return the PLATFORM_SEPOLICY_VERSION build flag available in framework
+     * compatibility matrix.
+     *
+     * @hide
+     */
+    @TestApi
+    public static native @NonNull String getPlatformSepolicyVersion();
+
+    /**
      * @return a list of VNDK snapshots supported by the framework, as
      * specified in framework manifest. For example,
      * [("27", ["libjpeg.so", "libbase.so"]),
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/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
index f6d525c..3cffeb0 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.graphics.FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
+
 import android.annotation.IntDef;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -172,8 +174,6 @@
      **/
     public static final int DEADLINE = 13;
 
-    private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
-
     /**
      * Identifiers for metrics available for each frame.
      *
@@ -343,7 +343,7 @@
         }
 
         if (id == FIRST_DRAW_FRAME) {
-            return (mTimingData[Index.FLAGS] & FRAME_INFO_FLAG_FIRST_DRAW) != 0 ? 1 : 0;
+            return (mTimingData[Index.FLAGS] & FLAG_WINDOW_VISIBILITY_CHANGED) != 0 ? 1 : 0;
         } else if (id == INTENDED_VSYNC_TIMESTAMP) {
             return mTimingData[Index.INTENDED_VSYNC];
         } else if (id == VSYNC_TIMESTAMP) {
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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0958f3f..a06f193 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2764,7 +2764,9 @@
                         // to resume them
                         mDirty.set(0, 0, mWidth, mHeight);
                     }
-                    mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED;
+                }
+                if (mFirst || viewVisibilityChanged) {
+                    mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
                 }
                 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
                 final boolean freeformResizing = (relayoutResult
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/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index 541b494..7726086 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -315,6 +315,8 @@
     private static class UiTranslationStateRemoteCallback extends IRemoteCallback.Stub {
         private final Executor mExecutor;
         private final UiTranslationStateCallback mCallback;
+        private ULocale mSourceLocale;
+        private ULocale mTargetLocale;
 
         UiTranslationStateRemoteCallback(Executor executor,
                 UiTranslationStateCallback callback) {
@@ -331,10 +333,12 @@
             int state = bundle.getInt(EXTRA_STATE);
             switch (state) {
                 case STATE_UI_TRANSLATION_STARTED:
+                    mSourceLocale = (ULocale) bundle.getSerializable(EXTRA_SOURCE_LOCALE);
+                    mTargetLocale = (ULocale) bundle.getSerializable(EXTRA_TARGET_LOCALE);
+                    mCallback.onStarted(mSourceLocale, mTargetLocale);
+                    break;
                 case STATE_UI_TRANSLATION_RESUMED:
-                    mCallback.onStarted(
-                            (ULocale) bundle.getSerializable(EXTRA_SOURCE_LOCALE),
-                            (ULocale) bundle.getSerializable(EXTRA_TARGET_LOCALE));
+                    mCallback.onStarted(mSourceLocale, mTargetLocale);
                     break;
                 case STATE_UI_TRANSLATION_PAUSED:
                     mCallback.onPaused();
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/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/jni/android_media_AudioAttributes.cpp b/core/jni/android_media_AudioAttributes.cpp
index b616ffc..f1ae268 100644
--- a/core/jni/android_media_AudioAttributes.cpp
+++ b/core/jni/android_media_AudioAttributes.cpp
@@ -171,10 +171,6 @@
 /*
  * JNI registration.
  */
-static const JNINativeMethod gMethods[] = {
-    // n/a
-};
-
 int register_android_media_AudioAttributes(JNIEnv *env)
 {
     jclass audioAttributesClass = FindClassOrDie(env, kClassPathName);
@@ -218,5 +214,5 @@
 
     env->DeleteLocalRef(audioAttributesClass);
 
-    return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
+    return 0;
 }
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 4bd33a9..1baea2a 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -37,11 +37,13 @@
 
 namespace android {
 
+using vintf::CompatibilityMatrix;
 using vintf::HalManifest;
 using vintf::Level;
 using vintf::SchemaType;
 using vintf::to_string;
 using vintf::toXml;
+using vintf::Version;
 using vintf::VintfObject;
 using vintf::Vndk;
 
@@ -119,6 +121,28 @@
     return env->NewStringUTF(cString.c_str());
 }
 
+static jstring android_os_VintfObject_getPlatformSepolicyVersion(JNIEnv* env, jclass) {
+    std::shared_ptr<const CompatibilityMatrix> matrix =
+            VintfObject::GetFrameworkCompatibilityMatrix();
+    if (matrix == nullptr || matrix->type() != SchemaType::FRAMEWORK) {
+        jniThrowRuntimeException(env, "Cannot get framework compatibility matrix");
+        return nullptr;
+    }
+
+    auto versions = matrix->getSepolicyVersions();
+    if (versions.empty()) {
+        jniThrowRuntimeException(env,
+                                 "sepolicy_version in framework compatibility matrix is empty");
+        return nullptr;
+    }
+
+    Version latest;
+    for (const auto& range : versions) {
+        latest = std::max(latest, range.maxVer());
+    }
+    return env->NewStringUTF(to_string(latest).c_str());
+}
+
 static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) {
     std::shared_ptr<const HalManifest> manifest = VintfObject::GetFrameworkHalManifest();
     if (manifest == nullptr || manifest->type() != SchemaType::FRAMEWORK) {
@@ -145,12 +169,17 @@
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gVintfObjectMethods[] = {
-    {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
-    {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
-    {"getHalNamesAndVersions", "()[Ljava/lang/String;", (void*)android_os_VintfObject_getHalNamesAndVersions},
-    {"getSepolicyVersion", "()Ljava/lang/String;", (void*)android_os_VintfObject_getSepolicyVersion},
-    {"getVndkSnapshots", "()Ljava/util/Map;", (void*)android_os_VintfObject_getVndkSnapshots},
-    {"getTargetFrameworkCompatibilityMatrixVersion", "()Ljava/lang/Long;", (void*)android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion},
+        {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
+        {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
+        {"getHalNamesAndVersions", "()[Ljava/lang/String;",
+         (void*)android_os_VintfObject_getHalNamesAndVersions},
+        {"getSepolicyVersion", "()Ljava/lang/String;",
+         (void*)android_os_VintfObject_getSepolicyVersion},
+        {"getPlatformSepolicyVersion", "()Ljava/lang/String;",
+         (void*)android_os_VintfObject_getPlatformSepolicyVersion},
+        {"getVndkSnapshots", "()Ljava/util/Map;", (void*)android_os_VintfObject_getVndkSnapshots},
+        {"getTargetFrameworkCompatibilityMatrixVersion", "()Ljava/lang/Long;",
+         (void*)android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion},
 };
 
 const char* const kVintfObjectPathName = "android/os/VintfObject";
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4dc4bef..4b828ba 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3947,11 +3947,11 @@
 
     <!-- This permission is required among systems services when accessing
          tuner resource management related APIs or information.
-         <p>Protection level: signature|privileged
-         <p>Not for use by third-party applications.
+         <p>Protection level: signature|privileged|vendorPrivileged
+         <p>This should only be used by the OEM TvInputService.
          @hide -->
     <permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
-         android:protectionLevel="signature|privileged" />
+         android:protectionLevel="signature|privileged|vendorPrivileged" />
 
     <!-- This permission is required by Media Resource Manager Service when
          accessing its overridePid Api.
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/app/people/PeopleSpaceTileTest.java b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
index 3e2c4e9..5cee2c1 100644
--- a/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
+++ b/core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java
@@ -16,6 +16,9 @@
 
 package android.app.people;
 
+import static android.app.people.PeopleSpaceTile.SHOW_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertFalse;
@@ -178,6 +181,71 @@
     }
 
     @Test
+    public void testUserQuieted() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+        assertFalse(tile.isUserQuieted());
+
+        tile = new PeopleSpaceTile
+                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+                .setIsUserQuieted(true)
+                .build();
+        assertTrue(tile.isUserQuieted());
+    }
+
+    @Test
+    public void testCanBypassDnd() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+        assertFalse(tile.canBypassDnd());
+
+        tile = new PeopleSpaceTile
+                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+                .setCanBypassDnd(true)
+                .build();
+        assertTrue(tile.canBypassDnd());
+    }
+
+    @Test
+    public void testNotificationPolicyState() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+
+        tile = new PeopleSpaceTile
+                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+                .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+                .build();
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_IMPORTANT_CONVERSATIONS);
+    }
+
+    @Test
+    public void testPackageSuspended() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+        assertFalse(tile.isPackageSuspended());
+
+        tile = new PeopleSpaceTile
+                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+                .setIsPackageSuspended(true)
+                .build();
+        assertTrue(tile.isPackageSuspended());
+    }
+
+    @Test
+    public void testContactAffinity() {
+        PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
+                new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
+        assertThat(tile.getContactAffinity()).isEqualTo(0f);
+
+        tile = new PeopleSpaceTile
+                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
+                .setContactAffinity(1f)
+                .build();
+        assertThat(tile.getContactAffinity()).isEqualTo(1f);
+    }
+
+    @Test
     public void testStatuses() {
         PeopleSpaceTile tile = new PeopleSpaceTile.Builder(
                 new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps).build();
@@ -238,6 +306,11 @@
                 .setNotificationDataUri(Uri.parse("data"))
                 .setMessagesCount(2)
                 .setIntent(new Intent())
+                .setIsUserQuieted(true)
+                .setCanBypassDnd(false)
+                .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+                .setIsPackageSuspended(true)
+                .setContactAffinity(1f)
                 .build();
 
         Parcel parcel = Parcel.obtain();
@@ -261,6 +334,12 @@
         assertThat(readTile.getNotificationDataUri()).isEqualTo(tile.getNotificationDataUri());
         assertThat(readTile.getMessagesCount()).isEqualTo(tile.getMessagesCount());
         assertThat(readTile.getIntent().toString()).isEqualTo(tile.getIntent().toString());
+        assertThat(readTile.isUserQuieted()).isEqualTo(tile.isUserQuieted());
+        assertThat(readTile.canBypassDnd()).isEqualTo(tile.canBypassDnd());
+        assertThat(readTile.getNotificationPolicyState()).isEqualTo(
+                tile.getNotificationPolicyState());
+        assertThat(readTile.isPackageSuspended()).isEqualTo(tile.isPackageSuspended());
+        assertThat(readTile.getContactAffinity()).isEqualTo(tile.getContactAffinity());
     }
 
     @Test
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/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java
index b9393ff..b3615ff 100644
--- a/graphics/java/android/graphics/FrameInfo.java
+++ b/graphics/java/android/graphics/FrameInfo.java
@@ -46,7 +46,7 @@
     public static final int FLAGS = 0;
 
     // Is this the first-draw following a window layout?
-    public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
+    public static final long FLAG_WINDOW_VISIBILITY_CHANGED = 1;
 
     // A renderer associated with just a Surface, not with a ViewRootImpl instance.
     public static final long FLAG_SURFACE_CANVAS = 1 << 2;
@@ -56,7 +56,7 @@
     public static final long INVALID_VSYNC_ID = -1;
 
     @LongDef(flag = true, value = {
-            FLAG_WINDOW_LAYOUT_CHANGED, FLAG_SURFACE_CANVAS })
+            FLAG_WINDOW_VISIBILITY_CHANGED, FLAG_SURFACE_CANVAS })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FrameInfoFlags {}
 
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/OneHandedAccessibilityUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAccessibilityUtil.java
index 1302461..4892e6b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAccessibilityUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAccessibilityUtil.java
@@ -82,7 +82,7 @@
 
     public void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "States: ");
+        pw.println(TAG);
         pw.print(innerPrefix + "mPackageName=");
         pw.println(mPackageName);
         pw.print(innerPrefix + "mDescription=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index 703eba9..481b948 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -203,7 +203,7 @@
 
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "states: ");
+        pw.println(TAG);
         pw.print(innerPrefix + "mIsShowing=");
         pw.println(mIsShowing);
         pw.print(innerPrefix + "mBkgBounds=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 04ec391..ae7ab52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -630,7 +630,8 @@
 
     public void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "States: ");
+        pw.println();
+        pw.println(TAG);
         pw.print(innerPrefix + "mOffSetFraction=");
         pw.println(mOffSetFraction);
         pw.print(innerPrefix + "mLockedDisabled=");
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 1c24338..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
@@ -305,7 +305,7 @@
 
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "states: ");
+        pw.println(TAG);
         pw.print(innerPrefix + "mDisplayLayout.rotation()=");
         pw.println(mDisplayLayout.rotation());
         pw.print(innerPrefix + "mDisplayAreaTokenMap=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
index 9e83a61..0383229 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedGestureHandler.java
@@ -276,7 +276,7 @@
 
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "States: ");
+        pw.println(TAG);
         pw.print(innerPrefix + "mAllowGesture=");
         pw.println(mAllowGesture);
         pw.print(innerPrefix + "mIsEnabled=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 1b2fcdd..2ab51f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -137,8 +137,8 @@
 
     void dump(PrintWriter pw, String prefix, ContentResolver resolver,
             int userId) {
-        final String innerPrefix = prefix + "  ";
-        pw.println(innerPrefix + TAG);
+        final String innerPrefix = "  ";
+        pw.println(TAG);
         pw.print(innerPrefix + "isOneHandedModeEnable=");
         pw.println(getSettingsOneHandedModeEnabled(resolver, userId));
         pw.print(innerPrefix + "oneHandedTimeOut=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java
index cc87443..facc4bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedState.java
@@ -90,7 +90,7 @@
     /** Dumps internal state. */
     public void dump(PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "states: ");
+        pw.println(TAG);
         pw.println(innerPrefix + "sCurrentState=" + sCurrentState);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
index 4a98941..899c9ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTimeoutHandler.java
@@ -18,8 +18,6 @@
 
 import static com.android.wm.shell.onehanded.OneHandedSettingsUtil.ONE_HANDED_TIMEOUT_MEDIUM_IN_SECONDS;
 
-import android.os.Handler;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
@@ -122,7 +120,7 @@
 
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "states: ");
+        pw.println(TAG);
         pw.print(innerPrefix + "sTimeout=");
         pw.println(mTimeout);
         pw.print(innerPrefix + "sListeners=");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
index c7a49ff..0f9b320 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTouchHandler.java
@@ -156,7 +156,7 @@
 
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + "states: ");
+        pw.println(TAG);
         pw.print(innerPrefix + "mLastUpdatedBounds=");
         pw.println(mLastUpdatedBounds);
     }
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 c60e903..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
@@ -224,7 +224,7 @@
 
     void dump(@NonNull PrintWriter pw) {
         final String innerPrefix = "  ";
-        pw.println(TAG + " states: ");
+        pw.println(TAG);
         pw.print(innerPrefix + "mTriggerState=");
         pw.println(mTriggerState);
         pw.print(innerPrefix + "mDisplayBounds=");
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java
index e29fc6a..aae1dd0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedUiEventLoggerTest.java
@@ -24,6 +24,7 @@
 import com.android.internal.logging.testing.UiEventLoggerFake;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -51,6 +52,7 @@
     }
 
     @Test
+    @Ignore("b/184813408, go/wm-tests showing test flaky")
     public void testLogEvent() {
         if (mUiEvent != null) {
             assertEquals(1, mUiEventLogger.numLogs());
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/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index 2a134fa..540a88b 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -68,7 +68,7 @@
 
 namespace FrameInfoFlags {
 enum {
-    WindowLayoutChanged = 1 << 0,
+    WindowVisibilityChanged = 1 << 0,
     RTAnimation = 1 << 1,
     SurfaceCanvas = 1 << 2,
     SkippedFrame = 1 << 3,
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/mime/java/android/content/type/DefaultMimeMapFactory.java b/mime/java/android/content/type/DefaultMimeMapFactory.java
index 11d20d4..bcd0eb0 100644
--- a/mime/java/android/content/type/DefaultMimeMapFactory.java
+++ b/mime/java/android/content/type/DefaultMimeMapFactory.java
@@ -96,7 +96,7 @@
                     specs.add(spec);
                     startIdx = endIdx + 1; // skip over the space
                 } while (startIdx < line.length());
-                builder.put(specs.get(0), specs.subList(1, specs.size()));
+                builder.addMimeMapping(specs.get(0), specs.subList(1, specs.size()));
             }
         } catch (IOException | RuntimeException e) {
             throw new RuntimeException("Failed to parse " + resourceName, e);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 399cf1f..33e5231 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -42,10 +42,17 @@
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        File sourceFile = new File(getIntent().getData().getPath());
-        sourceFile.delete();
-
         setResult(resultCode, data);
         finish();
     }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (isFinishing()) {
+            File sourceFile = new File(getIntent().getData().getPath());
+            new Thread(sourceFile::delete).start();
+        }
+    }
 }
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/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/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index ea644cf..c332f4c 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -254,7 +254,6 @@
                     android:layout_height="wrap_content"
                     android:layout_marginTop="@dimen/notification_importance_button_description_top_margin"
                     android:visibility="gone"
-                    android:text="@string/notification_channel_summary_priority"
                     android:clickable="false"
                     android:focusable="false"
                     android:ellipsize="end"
diff --git a/packages/SystemUI/res/layout/people_tile_empty_layout.xml b/packages/SystemUI/res/layout/people_tile_empty_layout.xml
new file mode 100644
index 0000000..7d3b919
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_tile_empty_layout.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:theme="@android:style/Theme.DeviceDefault.DayNight"
+    android:background="@drawable/people_space_tile_view_card"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/item"
+        android:gravity="center"
+        android:layout_gravity="center"
+        android:padding="8dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml b/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml
deleted file mode 100644
index 3f0e514..0000000
--- a/packages/SystemUI/res/layout/priority_onboarding_half_shell.xml
+++ /dev/null
@@ -1,166 +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
-  -->
-
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/onboarding_half_shell_container"
-    android:orientation="vertical"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_horizontal|bottom"
-    android:paddingStart="4dp"
-    android:paddingEnd="4dp"
-    >
-
-    <LinearLayout
-        android:id="@+id/half_shell"
-        android:layout_width="@dimen/qs_panel_width"
-        android:layout_height="wrap_content"
-        android:paddingTop="16dp"
-        android:paddingStart="16dp"
-        android:paddingEnd="16dp"
-        android:orientation="vertical"
-        android:gravity="bottom"
-        android:layout_gravity="center_horizontal|bottom"
-        android:background="@drawable/rounded_bg_full"
-        >
-
-    <FrameLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:clipChildren="false"
-        android:clipToPadding="false"
-        android:padding="12dp"
-        android:layout_gravity="center_horizontal"
-    >
-
-        <!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
-        <ImageView
-            android:id="@+id/conversation_icon"
-            android:layout_width="@*android:dimen/conversation_avatar_size"
-            android:layout_height="@*android:dimen/conversation_avatar_size"
-            android:scaleType="centerCrop"
-            android:importantForAccessibility="no"
-        />
-
-        <FrameLayout
-            android:id="@+id/conversation_icon_badge"
-            android:layout_width="@*android:dimen/conversation_icon_size_badged"
-            android:layout_height="@*android:dimen/conversation_icon_size_badged"
-            android:layout_marginLeft="@*android:dimen/conversation_badge_side_margin"
-            android:layout_marginTop="@*android:dimen/conversation_badge_side_margin"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-        >
-            <ImageView
-                android:id="@+id/conversation_icon_badge_bg"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_gravity="center"
-                android:src="@*android:drawable/conversation_badge_background"
-                android:forceHasOverlappingRendering="false"
-            />
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_margin="4dp"
-                android:layout_gravity="center"
-                android:forceHasOverlappingRendering="false"
-            />
-            <ImageView
-                android:id="@+id/conversation_icon_badge_ring"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:src="@*android:drawable/conversation_badge_ring"
-                android:forceHasOverlappingRendering="false"
-                android:clipToPadding="false"
-                android:scaleType="center"
-            />
-        </FrameLayout>
-    </FrameLayout>
-
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="center_horizontal"
-            android:layout_marginTop="16dp"
-            android:text="@string/priority_onboarding_title"
-            style="@style/TextAppearance.NotificationImportanceChannel"
-        />
-
-        <View
-            android:id="@+id/divider"
-            android:layout_width="match_parent"
-            android:layout_height="0.5dp"
-            android:layout_marginTop="20dp"
-            android:layout_marginBottom="20dp"
-            android:background="@color/material_grey_300" />
-
-        <TextView
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="start"
-            android:text="@string/priority_onboarding_behavior"
-            style="@style/TextAppearance.NotificationImportanceChannelGroup"
-        />
-
-        <TextView
-            android:id="@+id/behaviors"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:gravity="start"
-            android:layout_marginTop="8dp"
-            style="@style/TextAppearance.NotificationImportanceChannelGroup"
-        />
-
-        <!-- Bottom button container -->
-        <RelativeLayout
-            android:id="@+id/button_container"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="32dp"
-            android:orientation="horizontal"
-            >
-            <TextView
-                android:id="@+id/settings_button"
-                android:text="@string/priority_onboarding_settings_button_title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentStart="true"
-                android:gravity="start|center_vertical"
-                android:minWidth="@dimen/notification_importance_toggle_size"
-                android:minHeight="@dimen/notification_importance_toggle_size"
-                android:maxWidth="125dp"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
-            <TextView
-                android:id="@+id/done_button"
-                android:text="@string/priority_onboarding_done_button_title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentEnd="true"
-                android:gravity="end|center_vertical"
-                android:minWidth="@dimen/notification_importance_toggle_size"
-                android:minHeight="@dimen/notification_importance_toggle_size"
-                android:maxWidth="125dp"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
-
-        </RelativeLayout>
-
-    </LinearLayout>
-</FrameLayout>
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/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 673a03d..d9cc24f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1897,13 +1897,16 @@
     <string name="notification_channel_summary_automatic_demoted">&lt;b>Status:&lt;/b> Ranked Lower</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level summary -->
-    <string name="notification_channel_summary_priority">Always shown at the top of your notifications, even when Priority mode is on</string>
+    <string name="notification_channel_summary_priority_baseline">Shows at the top of conversation notifications and as a profile picture on lock screen</string>
+    <string name="notification_channel_summary_priority_bubble">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble</string>
+    <string name="notification_channel_summary_priority_dnd">Shows at the top of conversation notifications and as a profile picture on lock screen, interrupts Do Not Disturb</string>
+    <string name="notification_channel_summary_priority_all">Shows at the top of conversation notifications and as a profile picture on lock screen, appears as a bubble, interrupts Do Not Disturb</string>
 
     <!--[CHAR LIMIT=30] Linkable text to Settings app -->
     <string name="notification_conversation_channel_settings">Settings</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: important conversation level -->
-    <string name="notification_priority_title">Priority conversations</string>
+    <string name="notification_priority_title">Priority</string>
 
     <!-- Text shown in notification guts for conversation notifications that don't implement the full feature -->
     <string name="no_shortcut"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> doesn\u2019t support conversation features</string>
@@ -2667,25 +2670,6 @@
     <!-- Title of the overlay warning the user to interact with the device or it will go to sleep. [CHAR LIMIT=25] -->
     <string name="inattentive_sleep_warning_title">Standby</string>
 
-    <!-- Priority conversation onboarding screen -->
-    <!--  title of priority onboarding [CHAR LIMIT=75]  -->
-    <string name="priority_onboarding_title">Conversation set to priority</string>
-    <!--  Text explaining that the following actions are the behaviors of priority conversations.
-    E.g. priority conversations will show at the top of the conversation section [CHAR LIMIT=75]  -->
-    <string name="priority_onboarding_behavior">Priority conversations</string>
-    <!--  Text explaining that priority conversations show at the top of the conversation section [CHAR LIMIT=120]  -->
-    <string name="priority_onboarding_show_at_top_text">These conversations are shown at the top of your list and can always reach you when Priority mode is on</string>
-    <!--  Text explaining that priority conversations show an avatar on the lock screen [CHAR LIMIT=120]  -->
-    <string name="priority_onboarding_show_avatar_text">Profile pictures are shown on the lock screen</string>
-    <!--  Text explaining that priority conversations will appear as a bubble [CHAR LIMIT=120]  -->
-    <string name="priority_onboarding_appear_as_bubble_text">You can easily find these conversations in bubbles on your Home screen</string>
-    <!--  Text explaining that priority conversations can interrupt DnD settings [CHAR LIMIT=120]  -->
-    <string name="priority_onboarding_ignores_dnd_text">Interrupt Do Not Disturb</string>
-    <!--  Title for the affirmative button [CHAR LIMIT=50]  -->
-    <string name="priority_onboarding_done_button_title">Got it</string>
-    <!--  Title for the settings button button [CHAR LIMIT=50]  -->
-    <string name="priority_onboarding_settings_button_title">Settings</string>
-
     <!-- Window Magnification strings -->
     <!-- Title for Magnification Window [CHAR LIMIT=NONE] -->
     <string name="magnification_window_title">Magnification Window</string>
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/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index cce27e7..aa24bb4 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -26,5 +26,5 @@
     android:previewLayout="@layout/people_space_placeholder_layout"
     android:resizeMode="horizontal|vertical"
     android:configure="com.android.systemui.people.PeopleSpaceActivity"
-    android:initialLayout="@layout/people_space_initial_layout">
+    android:initialLayout="@layout/people_tile_empty_layout">
 </appwidget-provider>
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index f28d113..7f18379 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -73,8 +73,7 @@
             Key.TOUCHED_RINGER_TOGGLE,
             Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
             Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
-            Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
-            Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S
+            Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
     })
     // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
     public @interface Key {
@@ -123,8 +122,6 @@
         String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip";
         String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet";
         String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
-        /** Tracks whether the user has seen the onboarding screen for priority conversations */
-        String HAS_SEEN_PRIORITY_ONBOARDING_IN_S = "HasUserSeenPriorityOnboardingInS";
     }
 
     public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 2a7023a..0cf3333 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
 import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
+import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
 import com.android.systemui.screenshot.ActionProxyReceiver;
 import com.android.systemui.screenshot.DeleteScreenshotReceiver;
 import com.android.systemui.screenshot.SmartActionsReceiver;
@@ -79,4 +80,13 @@
     public abstract BroadcastReceiver bindPeopleSpaceWidgetPinnedReceiver(
             PeopleSpaceWidgetPinnedReceiver broadcastReceiver);
 
+    /**
+     *
+     */
+    @Binds
+    @IntoMap
+    @ClassKey(PeopleSpaceWidgetProvider.class)
+    public abstract BroadcastReceiver bindPeopleSpaceWidgetProvider(
+            PeopleSpaceWidgetProvider broadcastReceiver);
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 8e4e308..c04201c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -23,6 +23,7 @@
 import com.android.systemui.SystemUIAppComponentFactory;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.people.PeopleProvider;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.InjectionInflationController;
 import com.android.wm.shell.ShellCommandHandler;
@@ -156,4 +157,9 @@
      * Member injection into the supplied argument.
      */
     void inject(ClockOptionsProvider clockOptionsProvider);
+
+    /**
+     * Member injection into the supplied argument.
+     */
+    void inject(PeopleProvider peopleProvider);
 }
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/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
index a964056..0f66456 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
@@ -30,12 +30,15 @@
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.shared.system.PeopleProviderUtils;
 
+import javax.inject.Inject;
+
 /** API that returns a People Tile preview. */
 public class PeopleProvider extends ContentProvider {
     private static final String TAG = "PeopleProvider";
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
     private static final String EMPTY_STRING = "";
 
+    @Inject
     PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
 
     @Override
@@ -76,7 +79,8 @@
         }
 
         if (mPeopleSpaceWidgetManager == null) {
-            mPeopleSpaceWidgetManager = new PeopleSpaceWidgetManager(getContext());
+            Log.e(TAG, "Could not initialize people widget manager");
+            return null;
         }
         RemoteViews view =
                 mPeopleSpaceWidgetManager.getPreview(shortcutId, userHandle, packageName, extras);
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
index 99a17ffb..a6c6103 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -23,11 +23,11 @@
 import static com.android.systemui.people.NotificationHelper.isMissedCall;
 import static com.android.systemui.people.NotificationHelper.shouldMatchNotificationByUri;
 
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.people.ConversationChannel;
 import android.app.people.IPeopleManager;
 import android.app.people.PeopleSpaceTile;
-import android.appwidget.AppWidgetManager;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherApps;
@@ -40,13 +40,11 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.ContactsContract;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.Log;
-import android.widget.RemoteViews;
 
 import androidx.preference.PreferenceManager;
 
@@ -56,7 +54,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.R;
-import com.android.systemui.people.widget.AppWidgetOptionsHelper;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.people.widget.PeopleTileKey;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -76,45 +74,17 @@
 public class PeopleSpaceUtils {
     /** Turns on debugging information about People Space. */
     public static final boolean DEBUG = true;
-    private static final String TAG = "PeopleSpaceUtils";
     public static final String PACKAGE_NAME = "package_name";
     public static final String USER_ID = "user_id";
     public static final String SHORTCUT_ID = "shortcut_id";
-
     public static final String EMPTY_STRING = "";
     public static final int INVALID_USER_ID = -1;
-
     public static final PeopleTileKey EMPTY_KEY =
             new PeopleTileKey(EMPTY_STRING, INVALID_USER_ID, EMPTY_STRING);
-
-    /** Represents whether {@link StatusBarNotification} was posted or removed. */
-    public enum NotificationAction {
-        POSTED,
-        REMOVED
-    }
-
-    /**
-     * The UiEvent enums that this class can log.
-     */
-    public enum PeopleSpaceWidgetEvent implements UiEventLogger.UiEventEnum {
-        @UiEvent(doc = "People space widget deleted")
-        PEOPLE_SPACE_WIDGET_DELETED(666),
-        @UiEvent(doc = "People space widget added")
-        PEOPLE_SPACE_WIDGET_ADDED(667),
-        @UiEvent(doc = "People space widget clicked to launch conversation")
-        PEOPLE_SPACE_WIDGET_CLICKED(668);
-
-        private final int mId;
-
-        PeopleSpaceWidgetEvent(int id) {
-            mId = id;
-        }
-
-        @Override
-        public int getId() {
-            return mId;
-        }
-    }
+    static final float STARRED_CONTACT = 1f;
+    static final float VALID_CONTACT = .5f;
+    static final float DEFAULT_AFFINITY = 0f;
+    private static final String TAG = "PeopleSpaceUtils";
 
     /** Returns stored widgets for the conversation specified. */
     public static Set<String> getStoredWidgetIds(SharedPreferences sp, PeopleTileKey key) {
@@ -127,6 +97,10 @@
     /** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */
     public static void setSharedPreferencesStorageForTile(Context context, PeopleTileKey key,
             int appWidgetId, Uri contactUri) {
+        if (!key.isValid()) {
+            Log.e(TAG, "Not storing for invalid key");
+            return;
+        }
         // Write relevant persisted storage.
         SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId),
                 Context.MODE_PRIVATE);
@@ -201,7 +175,7 @@
                 .collect(Collectors.toList());
     }
 
-    /** Returns the total messages in {@code notificationEntries}.*/
+    /** Returns the total messages in {@code notificationEntries}. */
     public static int getMessagesCount(Set<NotificationEntry> notificationEntries) {
         if (DEBUG) {
             Log.d(TAG, "Calculating messages count from " + notificationEntries.size()
@@ -247,19 +221,34 @@
      * {@code messagesCount}.
      */
     public static PeopleSpaceTile augmentTileFromNotification(Context context, PeopleSpaceTile tile,
-            PeopleTileKey key, NotificationEntry notificationEntry, int messagesCount) {
+            PeopleTileKey key, NotificationEntry notificationEntry, int messagesCount,
+            Optional<Integer> appWidgetId) {
         if (notificationEntry == null || notificationEntry.getSbn().getNotification() == null) {
             if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification is null");
             return removeNotificationFields(tile);
         }
         Notification notification = notificationEntry.getSbn().getNotification();
+
+        PeopleSpaceTile.Builder updatedTile = tile.toBuilder();
+        String uriFromNotification = getContactUri(notificationEntry.getSbn());
+        if (appWidgetId.isPresent() && tile.getContactUri() == null && !TextUtils.isEmpty(
+                uriFromNotification)) {
+            if (DEBUG) Log.d(TAG, "Add uri from notification to tile: " + uriFromNotification);
+            Uri contactUri = Uri.parse(uriFromNotification);
+            // Update storage.
+            setSharedPreferencesStorageForTile(context, new PeopleTileKey(tile), appWidgetId.get(),
+                    contactUri);
+            // Update cached tile in-memory.
+            updatedTile.setContactUri(contactUri);
+        }
+
         boolean isMissedCall = isMissedCall(notification);
         List<Notification.MessagingStyle.Message> messages =
                 getMessagingStyleMessages(notification);
 
         if (!isMissedCall && ArrayUtils.isEmpty(messages)) {
             if (DEBUG) Log.d(TAG, "Tile key: " + key.toString() + ". Notification has no content");
-            return removeNotificationFields(tile);
+            return removeNotificationFields(updatedTile.build());
         }
 
         // messages are in chronological order from most recent to least.
@@ -276,8 +265,7 @@
         }
         CharSequence sender = getSenderIfGroupConversation(notification, message);
 
-        return tile
-                .toBuilder()
+        return updatedTile
                 .setNotificationKey(notificationEntry.getSbn().getKey())
                 .setNotificationCategory(notification.category)
                 .setNotificationContent(content)
@@ -378,17 +366,18 @@
                 context.getString(R.string.birthday_status));
     }
 
-    /** Calls to retrieve birthdays on a background thread. */
-    public static void getBirthdaysOnBackgroundThread(Context context,
-            AppWidgetManager appWidgetManager,
+    /** Calls to retrieve birthdays & contact affinity on a background thread. */
+    public static void getDataFromContactsOnBackgroundThread(Context context,
+            PeopleSpaceWidgetManager manager,
             Map<Integer, PeopleSpaceTile> peopleSpaceTiles, int[] appWidgetIds) {
         ThreadUtils.postOnBackgroundThread(
-                () -> getBirthdays(context, appWidgetManager, peopleSpaceTiles, appWidgetIds));
+                () -> getDataFromContacts(context, manager, peopleSpaceTiles, appWidgetIds));
     }
 
-    /** Queries the Contacts DB for any birthdays today. */
+    /** Queries the Contacts DB for any birthdays today & updates contact affinity. */
     @VisibleForTesting
-    public static void getBirthdays(Context context, AppWidgetManager appWidgetManager,
+    public static void getDataFromContacts(Context context,
+            PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             Map<Integer, PeopleSpaceTile> widgetIdToTile, int[] appWidgetIds) {
         if (DEBUG) Log.d(TAG, "Get birthdays");
         if (appWidgetIds.length == 0) return;
@@ -397,28 +386,37 @@
             PeopleSpaceTile storedTile = widgetIdToTile.get(appWidgetId);
             if (storedTile == null || storedTile.getContactUri() == null) {
                 if (DEBUG) Log.d(TAG, "No contact uri for: " + storedTile);
-                removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
+                updateTileContactFields(peopleSpaceWidgetManager, context, storedTile,
+                        appWidgetId, DEFAULT_AFFINITY, /* birthdayString= */ null);
                 continue;
             }
-            if (lookupKeysWithBirthdaysToday.isEmpty()) {
-                if (DEBUG) Log.d(TAG, "No birthdays today");
-                removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
-                continue;
-            }
-            updateTileWithBirthday(context, appWidgetManager, lookupKeysWithBirthdaysToday,
+            updateTileWithBirthdayAndUpdateAffinity(context, peopleSpaceWidgetManager,
+                    lookupKeysWithBirthdaysToday,
                     storedTile,
                     appWidgetId);
         }
     }
 
-    /** Removes the birthday status if present in {@code storedTile} and pushes the update. */
-    private static void removeBirthdayStatusIfPresent(AppWidgetManager appWidgetManager,
-            Context context, PeopleSpaceTile storedTile, int appWidgetId) {
-        if (hasBirthdayStatus(storedTile, context)) {
-            if (DEBUG) Log.d(TAG, "Remove " + storedTile.getUserName() + "'s birthday");
-            updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId,
+    /**
+     * Updates the {@code storedTile} with {@code affinity} & {@code birthdayString} if
+     * necessary.
+     */
+    private static void updateTileContactFields(PeopleSpaceWidgetManager manager,
+            Context context, PeopleSpaceTile storedTile, int appWidgetId, float affinity,
+            @Nullable String birthdayString) {
+        boolean outdatedBirthdayStatus = hasBirthdayStatus(storedTile, context)
+                && birthdayString == null;
+        boolean addBirthdayStatus = !hasBirthdayStatus(storedTile, context)
+                && birthdayString != null;
+        boolean shouldUpdate =
+                storedTile.getContactAffinity() != affinity || outdatedBirthdayStatus
+                        || addBirthdayStatus;
+        if (shouldUpdate) {
+            if (DEBUG) Log.d(TAG, "Update " + storedTile.getUserName() + " from contacts");
+            manager.updateAppWidgetOptionsAndView(appWidgetId,
                     storedTile.toBuilder()
-                            .setBirthdayText(null)
+                            .setBirthdayText(birthdayString)
+                            .setContactAffinity(affinity)
                             .build());
         }
     }
@@ -427,7 +425,8 @@
      * Update {@code storedTile} if the contact has a lookup key matched to any {@code
      * lookupKeysWithBirthdays}.
      */
-    private static void updateTileWithBirthday(Context context, AppWidgetManager appWidgetManager,
+    private static void updateTileWithBirthdayAndUpdateAffinity(Context context,
+            PeopleSpaceWidgetManager manager,
             List<String> lookupKeysWithBirthdaysToday, PeopleSpaceTile storedTile,
             int appWidgetId) {
         Cursor cursor = null;
@@ -437,14 +436,16 @@
             while (cursor != null && cursor.moveToNext()) {
                 String storedLookupKey = cursor.getString(
                         cursor.getColumnIndex(ContactsContract.CommonDataKinds.Event.LOOKUP_KEY));
+                float affinity = getContactAffinity(cursor);
                 if (!storedLookupKey.isEmpty() && lookupKeysWithBirthdaysToday.contains(
                         storedLookupKey)) {
                     if (DEBUG) Log.d(TAG, storedTile.getUserName() + "'s birthday today!");
-                    updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId,
-                            storedTile.toBuilder()
-                                    .setBirthdayText(context.getString(R.string.birthday_status))
-                                    .build());
-                    return;
+                    updateTileContactFields(manager, context, storedTile, appWidgetId,
+                            affinity, /* birthdayString= */
+                            context.getString(R.string.birthday_status));
+                } else {
+                    updateTileContactFields(manager, context, storedTile, appWidgetId,
+                            affinity, /* birthdayString= */ null);
                 }
             }
         } catch (SQLException e) {
@@ -454,51 +455,20 @@
                 cursor.close();
             }
         }
-        removeBirthdayStatusIfPresent(appWidgetManager, context, storedTile, appWidgetId);
     }
 
-    /** Updates the current widget view with provided {@link PeopleSpaceTile}. */
-    public static void updateAppWidgetViews(AppWidgetManager appWidgetManager,
-            Context context, int appWidgetId, PeopleSpaceTile tile, Bundle options) {
-        if (tile == null) {
-            if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ". Tile is null, skipping update");
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName() + ", "
-                    + tile.getPackageName() + ". Updating app widget view.");
-        }
-        RemoteViews views = new PeopleTileViewHelper(context, tile, appWidgetId,
-                options).getViews();
-
-        // Tell the AppWidgetManager to perform an update on the current app widget.
-        appWidgetManager.updateAppWidget(appWidgetId, views);
-    }
-
-    /** Updates tile in app widget options and the current view. */
-    public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
-            Context context, int appWidgetId, PeopleSpaceTile tile) {
-        if (tile == null) {
-            if (DEBUG) {
-                Log.w(TAG, "Widget: " + appWidgetId + "Tile is null, skipping storage and update.");
+    /** Pulls the contact affinity from {@code cursor}. */
+    private static float getContactAffinity(Cursor cursor) {
+        float affinity = VALID_CONTACT;
+        int starIdx = cursor.getColumnIndex(ContactsContract.Contacts.STARRED);
+        if (starIdx >= 0) {
+            boolean isStarred = cursor.getInt(starIdx) != 0;
+            if (isStarred) {
+                affinity = Math.max(affinity, STARRED_CONTACT);
             }
-            return;
         }
-        Bundle options = AppWidgetOptionsHelper.setPeopleTile(appWidgetManager, appWidgetId, tile);
-        updateAppWidgetViews(appWidgetManager, context, appWidgetId, tile, options);
-    }
-
-    /** Wrapper around {@link #updateAppWidgetOptionsAndView} with optional tile as a parameter. */
-    public static void updateAppWidgetOptionsAndView(AppWidgetManager appWidgetManager,
-            Context context, int appWidgetId, Optional<PeopleSpaceTile> optionalTile) {
-        if (!optionalTile.isPresent()) {
-            if (DEBUG) {
-                Log.w(TAG, "Widget: " + appWidgetId
-                        + "Optional tile is not present, skipping storage and update.");
-            }
-            return;
-        }
-        updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, optionalTile.get());
+        if (DEBUG) Log.d(TAG, "Affinity is: " + affinity);
+        return affinity;
     }
 
     /**
@@ -546,4 +516,33 @@
     public static int getUserId(PeopleSpaceTile tile) {
         return tile.getUserHandle().getIdentifier();
     }
+
+    /** Represents whether {@link StatusBarNotification} was posted or removed. */
+    public enum NotificationAction {
+        POSTED,
+        REMOVED
+    }
+
+    /**
+     * The UiEvent enums that this class can log.
+     */
+    public enum PeopleSpaceWidgetEvent implements UiEventLogger.UiEventEnum {
+        @UiEvent(doc = "People space widget deleted")
+        PEOPLE_SPACE_WIDGET_DELETED(666),
+        @UiEvent(doc = "People space widget added")
+        PEOPLE_SPACE_WIDGET_ADDED(667),
+        @UiEvent(doc = "People space widget clicked to launch conversation")
+        PEOPLE_SPACE_WIDGET_CLICKED(668);
+
+        private final int mId;
+
+        PeopleSpaceWidgetEvent(int id) {
+            mId = id;
+        }
+
+        @Override
+        public int getId() {
+            return mId;
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index a23db63..6980d72 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -30,6 +30,8 @@
 import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
 import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
 
+import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
+import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
 import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap;
 import static com.android.systemui.people.PeopleSpaceUtils.getUserId;
 
@@ -39,6 +41,8 @@
 import android.app.people.PeopleSpaceTile;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
@@ -48,6 +52,7 @@
 import android.icu.util.MeasureUnit;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.IconDrawableFactory;
 import android.util.Log;
@@ -57,6 +62,8 @@
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -142,7 +149,9 @@
     private int mMediumVerticalPadding;
 
     private Context mContext;
+    @Nullable
     private PeopleSpaceTile mTile;
+    private PeopleTileKey mKey;
     private float mDensity;
     private int mAppWidgetId;
     private int mWidth;
@@ -152,10 +161,11 @@
     private Locale mLocale;
     private NumberFormat mIntegerFormat;
 
-    public PeopleTileViewHelper(Context context, PeopleSpaceTile tile,
-            int appWidgetId, Bundle options) {
+    public PeopleTileViewHelper(Context context, @Nullable PeopleSpaceTile tile,
+            int appWidgetId, Bundle options, PeopleTileKey key) {
         mContext = context;
         mTile = tile;
+        mKey = key;
         mAppWidgetId = appWidgetId;
         mDensity = mContext.getResources().getDisplayMetrics().density;
         int display = mContext.getResources().getConfiguration().orientation;
@@ -184,8 +194,19 @@
      * content, then birthdays, then the most recent status, and finally last interaction.
      */
     private RemoteViews getViewForTile() {
-        PeopleTileKey key = new PeopleTileKey(mTile);
-        if (DEBUG) Log.d(TAG, "Creating view for tile key: " + key.toString());
+        if (DEBUG) Log.d(TAG, "Creating view for tile key: " + mKey.toString());
+        if (mTile == null || mTile.isPackageSuspended() || mTile.isUserQuieted()) {
+            if (DEBUG) Log.d(TAG, "Create empty view: " + mTile);
+            return createEmptyView();
+        }
+
+        boolean dndBlockingTileData = isDndBlockingTileData(mTile);
+        if (dndBlockingTileData) {
+            if (DEBUG) Log.d(TAG, "Create DND view: " + mTile.getNotificationPolicyState());
+            // TODO: Create DND view.
+            return createEmptyView();
+        }
+
         if (Objects.equals(mTile.getNotificationCategory(), CATEGORY_MISSED_CALL)) {
             if (DEBUG) Log.d(TAG, "Create missed call view");
             return createMissedCallRemoteViews();
@@ -217,6 +238,58 @@
         return createLastInteractionRemoteViews();
     }
 
+    private boolean isDndBlockingTileData(PeopleSpaceTile tile) {
+        int notificationPolicyState = tile.getNotificationPolicyState();
+        if ((notificationPolicyState & PeopleSpaceTile.SHOW_CONVERSATIONS) != 0) {
+            // Not in DND, or all conversations
+            if (DEBUG) Log.d(TAG, "Tile can show all data: " + tile.getUserName());
+            return false;
+        }
+        if ((notificationPolicyState & PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS) != 0
+                && tile.isImportantConversation()) {
+            if (DEBUG) Log.d(TAG, "Tile can show important: " + tile.getUserName());
+            return false;
+        }
+        if ((notificationPolicyState & PeopleSpaceTile.SHOW_STARRED_CONTACTS) != 0
+                && tile.getContactAffinity() == STARRED_CONTACT) {
+            if (DEBUG) Log.d(TAG, "Tile can show starred: " + tile.getUserName());
+            return false;
+        }
+        if ((notificationPolicyState & PeopleSpaceTile.SHOW_CONTACTS) != 0
+                && (tile.getContactAffinity() == VALID_CONTACT
+                || tile.getContactAffinity() == STARRED_CONTACT)) {
+            if (DEBUG) Log.d(TAG, "Tile can show contacts: " + tile.getUserName());
+            return false;
+        }
+        if (DEBUG) Log.d(TAG, "Tile can show if can bypass DND: " + tile.getUserName());
+        return !tile.canBypassDnd();
+    }
+
+    private RemoteViews createEmptyView() {
+        RemoteViews views = new RemoteViews(mContext.getPackageName(),
+                R.layout.people_tile_empty_layout);
+        Drawable appIcon = getAppBadge(mKey.getPackageName(), mKey.getUserId());
+        Bitmap appIconAsBitmap = convertDrawableToBitmap(appIcon);
+        FastBitmapDrawable drawable = new FastBitmapDrawable(
+                appIconAsBitmap);
+        drawable.setIsDisabled(true);
+        Bitmap convertedBitmap = convertDrawableToBitmap(drawable);
+        views.setImageViewBitmap(R.id.item, convertedBitmap);
+        return views;
+    }
+
+    private Drawable getAppBadge(String packageName, int userId) {
+        Drawable badge = null;
+        try {
+            final ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId);
+            badge = Utils.getBadgedIcon(mContext, appInfo);
+        } catch (PackageManager.NameNotFoundException e) {
+            badge = mContext.getPackageManager().getDefaultActivityIcon();
+        }
+        return badge;
+    }
+
     private void setMaxLines(RemoteViews views, boolean showSender) {
         int textSize = mLayoutSize == LAYOUT_LARGE ? getSizeInDp(
                 R.dimen.content_text_size_for_medium)
@@ -337,6 +410,9 @@
     private RemoteViews setCommonRemoteViewsFields(RemoteViews views,
             int maxAvatarSize) {
         try {
+            if (mTile == null) {
+                return views;
+            }
             boolean isAvailable =
                     mTile.getStatuses() != null && mTile.getStatuses().stream().anyMatch(
                             c -> c.getAvailability() == AVAILABILITY_AVAILABLE);
@@ -367,13 +443,16 @@
                             | Intent.FLAG_ACTIVITY_CLEAR_TASK
                             | Intent.FLAG_ACTIVITY_NO_HISTORY
                             | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, mTile.getId());
+            activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, mKey.getShortcutId());
             activityIntent.putExtra(
-                    PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, mTile.getPackageName());
+                    PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, mKey.getPackageName());
             activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE,
-                    mTile.getUserHandle());
-            activityIntent.putExtra(
-                    PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY, mTile.getNotificationKey());
+                    new UserHandle(mKey.getUserId()));
+            if (mTile != null) {
+                activityIntent.putExtra(
+                        PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
+                        mTile.getNotificationKey());
+            }
             views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity(
                     mContext,
                     mAppWidgetId,
@@ -727,6 +806,9 @@
                         c -> c.getActivity() == ACTIVITY_NEW_STORY);
 
         Icon icon = tile.getUserIcon();
+        if (icon == null) {
+            return null;
+        }
         PeopleStoryIconFactory storyIcon = new PeopleStoryIconFactory(context,
                 context.getPackageManager(),
                 IconDrawableFactory.newInstance(context, false),
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 64a6509..ea1724f 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -17,6 +17,12 @@
 package com.android.systemui.people.widget;
 
 import static android.Manifest.permission.READ_CONTACTS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
 
 import static com.android.systemui.people.NotificationHelper.getContactUri;
 import static com.android.systemui.people.NotificationHelper.getHighestPriorityNotification;
@@ -30,13 +36,12 @@
 import static com.android.systemui.people.PeopleSpaceUtils.getMessagesCount;
 import static com.android.systemui.people.PeopleSpaceUtils.getNotificationsByUri;
 import static com.android.systemui.people.PeopleSpaceUtils.removeNotificationFields;
-import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetOptionsAndView;
-import static com.android.systemui.people.PeopleSpaceUtils.updateAppWidgetViews;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.app.people.ConversationChannel;
@@ -44,8 +49,11 @@
 import android.app.people.PeopleManager;
 import android.app.people.PeopleSpaceTile;
 import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
@@ -60,6 +68,8 @@
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
+import android.text.TextUtils;
 import android.util.Log;
 import android.widget.RemoteViews;
 
@@ -67,8 +77,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
-import com.android.settingslib.utils.ThreadUtils;
-import com.android.systemui.Dependency;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.people.NotificationHelper;
 import com.android.systemui.people.PeopleSpaceUtils;
 import com.android.systemui.people.PeopleTileViewHelper;
@@ -85,15 +96,15 @@
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.Executor;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import javax.inject.Inject;
-import javax.inject.Singleton;
 
 /** Manager for People Space widget. */
-@Singleton
+@SysUISingleton
 public class PeopleSpaceWidgetManager {
     private static final String TAG = "PeopleSpaceWidgetMgr";
     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
@@ -107,12 +118,15 @@
     private PeopleManager mPeopleManager;
     private NotificationEntryManager mNotificationEntryManager;
     private PackageManager mPackageManager;
-    private PeopleSpaceWidgetProvider mPeopleSpaceWidgetProvider;
     private INotificationManager mINotificationManager;
     private UserManager mUserManager;
+    private PeopleSpaceWidgetManager mManager;
     public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+    private NotificationManager mNotificationManager;
+    private BroadcastDispatcher mBroadcastDispatcher;
+    private Executor mBgExecutor;
     @GuardedBy("mLock")
-    public static Map<PeopleTileKey, PeopleSpaceWidgetProvider.TileConversationListener>
+    public static Map<PeopleTileKey, TileConversationListener>
             mListeners = new HashMap<>();
 
     @GuardedBy("mLock")
@@ -120,46 +134,91 @@
     // This is required because on notification removal, the contact Uri field is stripped and we
     // only have the notification key to determine which widget IDs should be updated.
     private Map<String, Set<String>> mNotificationKeyToWidgetIdsMatchedByUri = new HashMap<>();
-    private boolean mIsForTesting;
+    private boolean mRegisteredReceivers;
 
     @Inject
-    public PeopleSpaceWidgetManager(Context context) {
+    public PeopleSpaceWidgetManager(Context context, LauncherApps launcherApps,
+            NotificationEntryManager notificationEntryManager,
+            PackageManager packageManager, UserManager userManager,
+            NotificationManager notificationManager, BroadcastDispatcher broadcastDispatcher,
+            @Background Executor bgExecutor) {
         if (DEBUG) Log.d(TAG, "constructor");
         mContext = context;
         mAppWidgetManager = AppWidgetManager.getInstance(context);
         mIPeopleManager = IPeopleManager.Stub.asInterface(
                 ServiceManager.getService(Context.PEOPLE_SERVICE));
-        mLauncherApps = context.getSystemService(LauncherApps.class);
+        mLauncherApps = launcherApps;
         mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
-        mPeopleManager = mContext.getSystemService(PeopleManager.class);
-        mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
-        mPackageManager = mContext.getPackageManager();
-        mPeopleSpaceWidgetProvider = new PeopleSpaceWidgetProvider();
+        mPeopleManager = context.getSystemService(PeopleManager.class);
+        mNotificationEntryManager = notificationEntryManager;
+        mPackageManager = packageManager;
         mINotificationManager = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
-        mUserManager = context.getSystemService(UserManager.class);
+        mUserManager = userManager;
+        mNotificationManager = notificationManager;
+        mManager = this;
+        mBroadcastDispatcher = broadcastDispatcher;
+        mBgExecutor = bgExecutor;
+    }
+
+    /** Initializes {@PeopleSpaceWidgetManager}. */
+    public void init() {
+        synchronized (mLock) {
+            if (!mRegisteredReceivers) {
+                IntentFilter filter = new IntentFilter();
+                filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
+                filter.addAction(ACTION_BOOT_COMPLETED);
+                filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+                filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+                filter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+                filter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+                filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+                filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+                filter.addAction(Intent.ACTION_USER_UNLOCKED);
+                mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter,
+                        null /* executor */, UserHandle.ALL);
+                mRegisteredReceivers = true;
+            }
+        }
+    }
+
+    /** Listener for the shortcut data changes. */
+    public class TileConversationListener implements PeopleManager.ConversationListener {
+
+        @Override
+        public void onConversationUpdate(@NonNull ConversationChannel conversation) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Received updated conversation: "
+                                + conversation.getShortcutInfo().getLabel());
+            }
+            updateWidgetsWithConversationChanged(conversation);
+        }
     }
 
     /**
-     * AppWidgetManager setter used for testing.
+     * PeopleSpaceWidgetManager setter used for testing.
      */
     @VisibleForTesting
-    public void setAppWidgetManager(
+    PeopleSpaceWidgetManager(Context context,
             AppWidgetManager appWidgetManager, IPeopleManager iPeopleManager,
             PeopleManager peopleManager, LauncherApps launcherApps,
             NotificationEntryManager notificationEntryManager, PackageManager packageManager,
-            boolean isForTesting, PeopleSpaceWidgetProvider peopleSpaceWidgetProvider,
-            UserManager userManager, INotificationManager notificationManager) {
+            UserManager userManager, INotificationManager iNotificationManager,
+            NotificationManager notificationManager, @Background Executor executor) {
+        mContext = context;
         mAppWidgetManager = appWidgetManager;
         mIPeopleManager = iPeopleManager;
         mPeopleManager = peopleManager;
         mLauncherApps = launcherApps;
         mNotificationEntryManager = notificationEntryManager;
         mPackageManager = packageManager;
-        mIsForTesting = isForTesting;
-        mPeopleSpaceWidgetProvider = peopleSpaceWidgetProvider;
         mUserManager = userManager;
-        mINotificationManager = notificationManager;
+        mINotificationManager = iNotificationManager;
+        mNotificationManager = notificationManager;
+        mManager = this;
+        mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        mBgExecutor = executor;
     }
 
     /**
@@ -173,7 +232,7 @@
                 return;
             }
 
-            if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets");
+            if (DEBUG) Log.d(TAG, "updating " + widgetIds.length + " widgets: " + widgetIds);
             synchronized (mLock) {
                 updateSingleConversationWidgets(widgetIds);
             }
@@ -191,16 +250,47 @@
         for (int appWidgetId : appWidgetIds) {
             PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
             if (tile == null) {
-                if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID");
-                //TODO: Delete app widget id when crash is fixed (b/172932636)
-                continue;
+                Log.e(TAG, "Matching conversation not found for shortcut ID");
             }
             Bundle options = mAppWidgetManager.getAppWidgetOptions(appWidgetId);
-            updateAppWidgetViews(mAppWidgetManager, mContext, appWidgetId, tile, options);
+            updateAppWidgetViews(appWidgetId, tile, options);
             widgetIdToTile.put(appWidgetId, tile);
+            if (tile != null) {
+                registerConversationListenerIfNeeded(appWidgetId,
+                        new PeopleTileKey(tile));
+            }
         }
-        PeopleSpaceUtils.getBirthdaysOnBackgroundThread(
-                mContext, mAppWidgetManager, widgetIdToTile, appWidgetIds);
+        PeopleSpaceUtils.getDataFromContactsOnBackgroundThread(
+                mContext, mManager, widgetIdToTile, appWidgetIds);
+    }
+
+    /** Updates the current widget view with provided {@link PeopleSpaceTile}. */
+    private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
+        PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
+        if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
+        if (!key.isValid()) {
+            Log.e(TAG, "Cannot update invalid widget");
+            return;
+        }
+        RemoteViews views = new PeopleTileViewHelper(mContext, tile, appWidgetId,
+                options, key).getViews();
+
+        // Tell the AppWidgetManager to perform an update on the current app widget.
+        mAppWidgetManager.updateAppWidget(appWidgetId, views);
+    }
+
+    /** Updates tile in app widget options and the current view. */
+    public void updateAppWidgetOptionsAndViewOptional(int appWidgetId,
+            Optional<PeopleSpaceTile> tile) {
+        if (tile.isPresent()) {
+            updateAppWidgetOptionsAndView(appWidgetId, tile.get());
+        }
+    }
+
+    /** Updates tile in app widget options and the current view. */
+    public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
+        Bundle options = AppWidgetOptionsHelper.setPeopleTile(mAppWidgetManager, appWidgetId, tile);
+        updateAppWidgetViews(appWidgetId, tile, options);
     }
 
     /**
@@ -226,7 +316,7 @@
                 widgetSp.getInt(USER_ID, INVALID_USER_ID),
                 widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
 
-        return getTileFromPersistentStorage(key);
+        return getTileFromPersistentStorage(key, appWidgetId);
     }
 
     /**
@@ -234,7 +324,7 @@
      * If a {@link PeopleTileKey} is not provided, fetch one from {@link SharedPreferences}.
      */
     @Nullable
-    public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key) {
+    public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId) {
         if (!key.isValid()) {
             Log.e(TAG, "PeopleTileKey invalid: " + key.toString());
             return null;
@@ -254,7 +344,22 @@
                 return null;
             }
 
-            return new PeopleSpaceTile.Builder(channel, mLauncherApps).build();
+            // Get tile from shortcut & conversation storage.
+            PeopleSpaceTile.Builder storedTile = new PeopleSpaceTile.Builder(channel,
+                    mLauncherApps);
+            if (storedTile == null) {
+                return storedTile.build();
+            }
+
+            // Supplement with our storage.
+            String contactUri = mSharedPrefs.getString(String.valueOf(appWidgetId), null);
+            if (contactUri != null && storedTile.build().getContactUri() == null) {
+                if (DEBUG) Log.d(TAG, "Restore contact uri from storage: " + contactUri);
+                storedTile.setContactUri(Uri.parse(contactUri));
+            }
+
+            // Add current state.
+            return updateWithCurrentState(storedTile.build(), ACTION_BOOT_COMPLETED);
         } catch (Exception e) {
             Log.e(TAG, "Failed to retrieve conversation for tile: " + e);
             return null;
@@ -275,11 +380,7 @@
                 Log.d(TAG, "Notification removed, key: " + sbn.getKey());
             }
         }
-        if (mIsForTesting) {
-            updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction);
-            return;
-        }
-        ThreadUtils.postOnBackgroundThread(
+        mBgExecutor.execute(
                 () -> updateWidgetsWithNotificationChangedInBackground(sbn, notificationAction));
     }
 
@@ -331,8 +432,7 @@
                     .collect(Collectors.toMap(
                             Function.identity(),
                             id -> getAugmentedTileForExistingWidget(id, groupedNotifications)))
-                    .forEach((id, tile) ->
-                            updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, id, tile));
+                    .forEach((id, tile) -> updateAppWidgetOptionsAndViewOptional(id, tile));
         } catch (Exception e) {
             Log.e(TAG, "Exception updating widgets: " + e);
         }
@@ -341,16 +441,20 @@
     /**
      * Augments {@code tile} based on notifications returned from {@code notificationEntryManager}.
      */
-    public PeopleSpaceTile augmentTileFromNotificationEntryManager(PeopleSpaceTile tile) {
+    public PeopleSpaceTile augmentTileFromNotificationEntryManager(PeopleSpaceTile tile,
+            Optional<Integer> appWidgetId) {
         PeopleTileKey key = new PeopleTileKey(tile);
-        Log.d(TAG, "Augmenting tile from NotificationEntryManager widget: " + key.toString());
+        if (DEBUG) {
+            Log.d(TAG,
+                    "Augmenting tile from NotificationEntryManager widget: " + key.toString());
+        }
         Map<PeopleTileKey, Set<NotificationEntry>> notifications =
                 getGroupedConversationNotifications();
         String contactUri = null;
         if (tile.getContactUri() != null) {
             contactUri = tile.getContactUri().toString();
         }
-        return augmentTileFromNotifications(tile, key, contactUri, notifications);
+        return augmentTileFromNotifications(tile, key, contactUri, notifications, appWidgetId);
     }
 
     /** Returns active and pending notifications grouped by {@link PeopleTileKey}. */
@@ -380,9 +484,11 @@
 
     /** Augments {@code tile} based on {@code notifications}, matching {@code contactUri}. */
     public PeopleSpaceTile augmentTileFromNotifications(PeopleSpaceTile tile, PeopleTileKey key,
-            String contactUri, Map<PeopleTileKey, Set<NotificationEntry>> notifications) {
+            String contactUri,
+            Map<PeopleTileKey, Set<NotificationEntry>> notifications,
+            Optional<Integer> appWidgetId) {
         if (DEBUG) Log.d(TAG, "Augmenting tile from notifications. Tile key: " + key.toString());
-        boolean hasReadContactsPermission =  mPackageManager.checkPermission(READ_CONTACTS,
+        boolean hasReadContactsPermission = mPackageManager.checkPermission(READ_CONTACTS,
                 tile.getPackageName()) == PackageManager.PERMISSION_GRANTED;
 
         List<NotificationEntry> notificationsByUri = new ArrayList<>();
@@ -413,7 +519,8 @@
         NotificationEntry highestPriority = getHighestPriorityNotification(allNotifications);
 
         if (DEBUG) Log.d(TAG, "Augmenting tile from notification, key: " + key.toString());
-        return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount);
+        return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount,
+                appWidgetId);
     }
 
     /** Returns an augmented tile for an existing widget. */
@@ -434,7 +541,8 @@
         PeopleTileKey key = new PeopleTileKey(tile);
         if (DEBUG) Log.d(TAG, "Existing widget: " + widgetId + ". Tile key: " + key.toString());
         return Optional.ofNullable(
-                augmentTileFromNotifications(tile, key, contactUriString, notifications));
+                augmentTileFromNotifications(tile, key, contactUriString, notifications,
+                        Optional.of(widgetId)));
     }
 
     /** Returns stored widgets for the conversation specified. */
@@ -552,10 +660,8 @@
                 .setStatuses(conversation.getStatuses())
                 .setLastInteractionTimestamp(conversation.getLastEventTimestamp())
                 .setIsImportantConversation(conversation.getParentNotificationChannel() != null
-                        && conversation.getParentNotificationChannel().isImportantConversation())
-                .build();
-        updateAppWidgetOptionsAndView(mAppWidgetManager, mContext, appWidgetId,
-                updatedTile.build());
+                        && conversation.getParentNotificationChannel().isImportantConversation());
+        updateAppWidgetOptionsAndView(appWidgetId, updatedTile.build());
     }
 
     /**
@@ -640,11 +746,11 @@
     /** Adds a widget based on {@code key} mapped to {@code appWidgetId}. */
     public void addNewWidget(int appWidgetId, PeopleTileKey key) {
         if (DEBUG) Log.d(TAG, "addNewWidget called with key for appWidgetId: " + appWidgetId);
-        PeopleSpaceTile tile = getTileFromPersistentStorage(key);
+        PeopleSpaceTile tile = getTileFromPersistentStorage(key, appWidgetId);
         if (tile == null) {
             return;
         }
-        tile = augmentTileFromNotificationEntryManager(tile);
+        tile = augmentTileFromNotificationEntryManager(tile, Optional.of(appWidgetId));
 
         PeopleTileKey existingKeyIfStored;
         synchronized (mLock) {
@@ -665,6 +771,8 @@
             PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
                     tile.getContactUri());
         }
+        if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId);
+        registerConversationListenerIfNeeded(appWidgetId, key);
         try {
             if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + key.toString());
             mLauncherApps.cacheShortcuts(tile.getPackageName(),
@@ -674,23 +782,17 @@
             Log.w(TAG, "Exception caching shortcut:" + e);
         }
 
-        PeopleSpaceUtils.updateAppWidgetOptionsAndView(
-                mAppWidgetManager, mContext, appWidgetId, tile);
-        mPeopleSpaceWidgetProvider.onUpdate(mContext, mAppWidgetManager, new int[]{appWidgetId});
+        updateAppWidgetOptionsAndView(appWidgetId, tile);
     }
 
     /** Registers a conversation listener for {@code appWidgetId} if not already registered. */
-    public void registerConversationListenerIfNeeded(int widgetId,
-            PeopleSpaceWidgetProvider.TileConversationListener newListener) {
+    public void registerConversationListenerIfNeeded(int widgetId, PeopleTileKey key) {
         // Retrieve storage needed for registration.
-        PeopleTileKey key;
-        synchronized (mLock) {
-            key = getKeyFromStorageByWidgetId(widgetId);
-            if (!key.isValid()) {
-                if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId);
-                return;
-            }
+        if (!key.isValid()) {
+            if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId);
+            return;
         }
+        TileConversationListener newListener = new TileConversationListener();
         synchronized (mListeners) {
             if (mListeners.containsKey(key)) {
                 if (DEBUG) Log.d(TAG, "Already registered listener");
@@ -760,7 +862,7 @@
 
     /** Unregisters the conversation listener for {@code appWidgetId}. */
     private void unregisterConversationListener(PeopleTileKey key, int appWidgetId) {
-        PeopleSpaceWidgetProvider.TileConversationListener registeredListener;
+        TileConversationListener registeredListener;
         synchronized (mListeners) {
             registeredListener = mListeners.get(key);
             if (registeredListener == null) {
@@ -875,9 +977,153 @@
             return null;
         }
 
-        PeopleSpaceTile augmentedTile = augmentTileFromNotificationEntryManager(tile);
+        PeopleSpaceTile augmentedTile = augmentTileFromNotificationEntryManager(tile,
+                Optional.empty());
 
         if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
-        return new PeopleTileViewHelper(mContext, augmentedTile, 0, options).getViews();
+        return new PeopleTileViewHelper(mContext, augmentedTile, 0, options,
+                new PeopleTileKey(augmentedTile)).getViews();
+    }
+
+    protected final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (DEBUG) Log.d(TAG, "Update widgets from: " + action);
+            mBgExecutor.execute(() -> updateWidgetsOnStateChange(action));
+        }
+    };
+
+    /** Updates any app widget based on the current state. */
+    @VisibleForTesting
+    void updateWidgetsOnStateChange(String entryPoint) {
+        int[] appWidgetIds = mAppWidgetManager.getAppWidgetIds(
+                new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
+        if (appWidgetIds == null) {
+            return;
+        }
+        synchronized (mLock) {
+            for (int appWidgetId : appWidgetIds) {
+                PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
+                if (tile == null) {
+                    Log.e(TAG, "Matching conversation not found for shortcut ID");
+                } else {
+                    tile = updateWithCurrentState(tile, entryPoint);
+                }
+                updateAppWidgetOptionsAndView(appWidgetId, tile);
+            }
+        }
+    }
+
+    /** Checks the current state of {@code tile} dependencies, updating fields as necessary. */
+    @Nullable
+    private PeopleSpaceTile updateWithCurrentState(PeopleSpaceTile tile,
+            String entryPoint) {
+        PeopleSpaceTile.Builder updatedTile = tile.toBuilder();
+        try {
+            switch (entryPoint) {
+                case NotificationManager
+                        .ACTION_INTERRUPTION_FILTER_CHANGED:
+                    updatedTile.setNotificationPolicyState(getNotificationPolicyState());
+                    break;
+                case Intent.ACTION_PACKAGES_SUSPENDED:
+                case Intent.ACTION_PACKAGES_UNSUSPENDED:
+                    updatedTile.setIsPackageSuspended(getPackageSuspended(tile));
+                    break;
+                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+                case Intent.ACTION_USER_UNLOCKED:
+                    updatedTile.setIsUserQuieted(getUserQuieted(tile));
+                    break;
+                case Intent.ACTION_LOCALE_CHANGED:
+                    break;
+                case ACTION_BOOT_COMPLETED:
+                default:
+                    updatedTile.setIsUserQuieted(getUserQuieted(tile)).setIsPackageSuspended(
+                            getPackageSuspended(tile)).setNotificationPolicyState(
+                            getNotificationPolicyState());
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Package no longer found for tile: " + tile.toString() + e);
+            return null;
+        }
+        return updatedTile.build();
+    }
+
+    private boolean getPackageSuspended(PeopleSpaceTile tile) throws Exception {
+        boolean packageSuspended = !TextUtils.isEmpty(tile.getPackageName())
+                && mPackageManager.isPackageSuspended(tile.getPackageName());
+        if (DEBUG) Log.d(TAG, "Package suspended: " + packageSuspended);
+        return packageSuspended;
+    }
+
+    private boolean getUserQuieted(PeopleSpaceTile tile) {
+        boolean workProfileQuieted =
+                tile.getUserHandle() != null && mUserManager.isQuietModeEnabled(
+                        tile.getUserHandle());
+        if (DEBUG) Log.d(TAG, "Work profile quiet: " + workProfileQuieted);
+        return workProfileQuieted;
+    }
+
+    private int getNotificationPolicyState() {
+        NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
+        boolean suppressVisualEffects =
+                NotificationManager.Policy.areAllVisualEffectsSuppressed(
+                        policy.suppressedVisualEffects);
+        int notificationPolicyState = 0;
+        switch (mNotificationManager.getCurrentInterruptionFilter()) {
+            case INTERRUPTION_FILTER_ALL:
+                if (DEBUG) Log.d(TAG, "All interruptions allowed");
+                return PeopleSpaceTile.SHOW_CONVERSATIONS;
+            case INTERRUPTION_FILTER_PRIORITY:
+                if (policy.allowConversations()) {
+                    // If the user sees notifications in DND, show notifications in tiles in DND.
+                    if (!suppressVisualEffects) {
+                        if (DEBUG) Log.d(TAG, "Visual effects not suppressed.");
+                        return PeopleSpaceTile.SHOW_CONVERSATIONS;
+                    }
+                    if (policy.priorityConversationSenders == CONVERSATION_SENDERS_ANYONE) {
+                        if (DEBUG) Log.d(TAG, "All conversations allowed");
+                        // We only show conversations, so we can show everything.
+                        return PeopleSpaceTile.SHOW_CONVERSATIONS;
+                    } else if (policy.priorityConversationSenders
+                            == NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT) {
+                        if (DEBUG) Log.d(TAG, "Important conversations allowed");
+                        notificationPolicyState |= PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+                    }
+                }
+                if (policy.allowMessages()) {
+                    switch (policy.allowMessagesFrom()) {
+                        case ZenModeConfig.SOURCE_CONTACT:
+                            if (DEBUG) Log.d(TAG, "All contacts allowed");
+                            notificationPolicyState |= PeopleSpaceTile.SHOW_CONTACTS;
+                            return notificationPolicyState;
+                        case ZenModeConfig.SOURCE_STAR:
+                            if (DEBUG) Log.d(TAG, "Starred contacts allowed");
+                            notificationPolicyState |= PeopleSpaceTile.SHOW_STARRED_CONTACTS;
+                            return notificationPolicyState;
+                        case ZenModeConfig.SOURCE_ANYONE:
+                        default:
+                            if (DEBUG) Log.d(TAG, "All messages allowed");
+                            return PeopleSpaceTile.SHOW_CONVERSATIONS;
+                    }
+                }
+                if (notificationPolicyState != 0) {
+                    if (DEBUG) Log.d(TAG, "Return block state: " + notificationPolicyState);
+                    return notificationPolicyState;
+                }
+                // If only alarms or nothing can bypass DND, the tile shouldn't show conversations.
+            case INTERRUPTION_FILTER_NONE:
+            case INTERRUPTION_FILTER_ALARMS:
+            default:
+                // If the user sees notifications in DND, show notifications in tiles in DND.
+                if (!suppressVisualEffects) {
+                    if (DEBUG) Log.d(TAG, "Visual effects not suppressed.");
+                    return PeopleSpaceTile.SHOW_CONVERSATIONS;
+                }
+                if (DEBUG) Log.d(TAG, "Block conversations");
+                return PeopleSpaceTile.BLOCK_CONVERSATIONS;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
index 3bc5b29..3522b76 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.people.widget;
 
-import android.annotation.NonNull;
-import android.app.people.ConversationChannel;
-import android.app.people.PeopleManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProvider;
 import android.content.Context;
@@ -28,6 +25,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.people.PeopleSpaceUtils;
 
+import javax.inject.Inject;
+
 /** People Space Widget Provider class. */
 public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
     private static final String TAG = "PeopleSpaceWidgetPvd";
@@ -38,25 +37,11 @@
     public static final String EXTRA_USER_HANDLE = "extra_user_handle";
     public static final String EXTRA_NOTIFICATION_KEY = "extra_notification_key";
 
-    public PeopleSpaceWidgetManager peopleSpaceWidgetManager;
+    public PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
 
-    /** Listener for the shortcut data changes. */
-    public class TileConversationListener implements PeopleManager.ConversationListener {
-
-        @Override
-        public void onConversationUpdate(@NonNull ConversationChannel conversation) {
-            if (DEBUG) {
-                Log.d(TAG,
-                        "Received updated conversation: "
-                                + conversation.getShortcutInfo().getLabel());
-            }
-            if (peopleSpaceWidgetManager == null) {
-                // This shouldn't happen since onUpdate is called at reboot.
-                Log.e(TAG, "Skipping conversation update: WidgetManager uninitialized");
-                return;
-            }
-            peopleSpaceWidgetManager.updateWidgetsWithConversationChanged(conversation);
-        }
+    @Inject
+    PeopleSpaceWidgetProvider(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
+        mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
     }
 
     /** Called when widget updates. */
@@ -65,15 +50,8 @@
         super.onUpdate(context, appWidgetManager, appWidgetIds);
 
         if (DEBUG) Log.d(TAG, "onUpdate called");
-        ensurePeopleSpaceWidgetManagerInitialized(context);
-        peopleSpaceWidgetManager.updateWidgets(appWidgetIds);
-        for (int appWidgetId : appWidgetIds) {
-            if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId);
-            PeopleSpaceWidgetProvider.TileConversationListener
-                    newListener = new PeopleSpaceWidgetProvider.TileConversationListener();
-            peopleSpaceWidgetManager.registerConversationListenerIfNeeded(appWidgetId,
-                    newListener);
-        }
+        ensurePeopleSpaceWidgetManagerInitialized();
+        mPeopleSpaceWidgetManager.updateWidgets(appWidgetIds);
     }
 
     /** Called when widget updates. */
@@ -81,25 +59,23 @@
     public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager,
             int appWidgetId, Bundle newOptions) {
         super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
-        ensurePeopleSpaceWidgetManagerInitialized(context);
-        peopleSpaceWidgetManager.onAppWidgetOptionsChanged(appWidgetId, newOptions);
+        ensurePeopleSpaceWidgetManagerInitialized();
+        mPeopleSpaceWidgetManager.onAppWidgetOptionsChanged(appWidgetId, newOptions);
     }
 
     @Override
     public void onDeleted(Context context, int[] appWidgetIds) {
         super.onDeleted(context, appWidgetIds);
-        ensurePeopleSpaceWidgetManagerInitialized(context);
-        peopleSpaceWidgetManager.deleteWidgets(appWidgetIds);
+        ensurePeopleSpaceWidgetManagerInitialized();
+        mPeopleSpaceWidgetManager.deleteWidgets(appWidgetIds);
     }
 
-    private void ensurePeopleSpaceWidgetManagerInitialized(Context context) {
-        if (peopleSpaceWidgetManager == null) {
-            peopleSpaceWidgetManager = new PeopleSpaceWidgetManager(context);
-        }
+    private void ensurePeopleSpaceWidgetManagerInitialized() {
+        mPeopleSpaceWidgetManager.init();
     }
 
     @VisibleForTesting
     public void setPeopleSpaceWidgetManager(PeopleSpaceWidgetManager manager) {
-        peopleSpaceWidgetManager = manager;
+        mPeopleSpaceWidgetManager = manager;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index c552e89..aa4fb71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -7,7 +7,6 @@
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -333,11 +332,6 @@
 
     @Override
     public boolean updateResources() {
-        // Update bottom padding, useful for removing extra space once the panel page indicator is
-        // hidden.
-        Resources res = getContext().getResources();
-        setPageMargin(res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal));
-
         setPadding(0, 0, 0,
                 getContext().getResources().getDimensionPixelSize(
                         R.dimen.qs_paged_tile_layout_padding_bottom));
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index f486c53..3b43676 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -207,11 +207,12 @@
                 mContext.getResources().getDimensionPixelSize(R.dimen.qs_container_bottom_padding)
         );
 
-        mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
+        int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
         int padding = getResources().getDimensionPixelSize(
                 R.dimen.notification_shade_content_margin_horizontal);
-        boolean marginsChanged = padding != mContentPadding;
+        boolean marginsChanged = padding != mContentPadding || sideMargins != mSideMargins;
         mContentPadding = padding;
+        mSideMargins = sideMargins;
         if (marginsChanged) {
             updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController);
         }
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/commandline/CommandRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
index 1da42a7..78e85f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt
@@ -168,7 +168,6 @@
 
         when (topLevel) {
             "list-prefs" -> listPrefs(pw)
-            "set-pref" -> setPref(pw, args.drop(1))
             else -> help(pw)
         }
     }
@@ -180,25 +179,4 @@
             pw.println(field.get(Prefs.Key::class.java))
         }
     }
-
-    /**
-     * Sets a preference from [Prefs]
-     */
-    private fun setPref(pw: PrintWriter, args: List<String>) {
-        if (args.isEmpty()) {
-            pw.println("invalid arguments: $args")
-            return
-        }
-        val pref = args[0]
-
-        when (pref) {
-            Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S -> {
-                val value = Integer.parseInt(args[1])
-                Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, value != 0)
-            }
-            else -> {
-                pw.println("Cannot set pref ($pref)")
-            }
-        }
-    }
 }
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/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 617dadb..e2a37f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -69,7 +69,6 @@
 import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
 import com.android.systemui.statusbar.phone.ShadeController;
@@ -141,7 +140,6 @@
             ShortcutManager shortcutManager,
             ChannelEditorDialogController channelEditorDialogController,
             UserContextProvider contextTracker,
-            Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
             Optional<BubblesManager> bubblesManagerOptional,
             UiEventLogger uiEventLogger,
@@ -161,7 +159,6 @@
                 shortcutManager,
                 channelEditorDialogController,
                 contextTracker,
-                builderProvider,
                 assistantFeedbackController,
                 bubblesManagerOptional,
                 uiEventLogger,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 40be4bf..1f4f3ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -22,6 +22,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
 
 import static com.android.systemui.animation.Interpolators.FAST_OUT_SLOW_IN;
 
@@ -34,7 +36,6 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
-import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -55,7 +56,6 @@
 import android.transition.TransitionSet;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ImageView;
@@ -64,7 +64,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -78,8 +77,6 @@
 import java.lang.annotation.Retention;
 import java.util.Optional;
 
-import javax.inject.Provider;
-
 /**
  * The guts of a conversation notification revealed when performing a long press.
  */
@@ -107,7 +104,6 @@
     private StatusBarNotification mSbn;
     @Nullable private Notification.BubbleMetadata mBubbleMetadata;
     private Context mUserContext;
-    private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
     private boolean mIsDeviceProvisioned;
     private int mAppBubble;
 
@@ -172,13 +168,9 @@
     private OnClickListener mOnDone = v -> {
         mPressedApply = true;
 
-        // If the user selected Priority, maybe show the priority onboarding.
         // If the user selected Priority and the previous selection was not priority, show a
-        // People Tile add request. If showing the priority onboarding, however, delay the request
-        // to when the onboarding dialog closes.
-        if (mSelectedAction == ACTION_FAVORITE && shouldShowPriorityOnboarding()) {
-            showPriorityOnboarding();
-        } else if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
+        // People Tile add request.
+        if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
             mShadeController.animateCollapsePanels();
             mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
         }
@@ -229,7 +221,6 @@
             OnSnoozeClickListener onSnoozeClickListener,
             ConversationIconFactory conversationIconFactory,
             Context userContext,
-            Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             boolean isDeviceProvisioned,
             @Main Handler mainHandler,
             @Background Handler bgHandler,
@@ -258,7 +249,6 @@
         mBubbleMetadata = bubbleMetadata;
         mBubblesManagerOptional = bubblesManagerOptional;
         mShadeController = shadeController;
-        mBuilderProvider = builderProvider;
         mMainHandler = mainHandler;
         mBgHandler = bgHandler;
         mShortcutManager = shortcutManager;
@@ -342,6 +332,18 @@
         // bindName();
         bindPackage();
         bindIcon(mNotificationChannel.isImportantConversation());
+
+        mPriorityDescriptionView = findViewById(R.id.priority_summary);
+        if (willShowAsBubble() && willBypassDnd()) {
+            mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_all);
+        } else if (willShowAsBubble()) {
+            mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_bubble);
+        } else if (willBypassDnd()) {
+            mPriorityDescriptionView.setText(R.string.notification_channel_summary_priority_dnd);
+        } else {
+            mPriorityDescriptionView.setText(
+                    R.string.notification_channel_summary_priority_baseline);
+        }
     }
 
     private void bindIcon(boolean important) {
@@ -428,7 +430,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mPriorityDescriptionView = findViewById(R.id.priority_summary);
         mDefaultDescriptionView = findViewById(R.id.default_summary);
         mSilentDescriptionView = findViewById(R.id.silence_summary);
     }
@@ -552,51 +553,22 @@
                 StackStateAnimator.ANIMATION_DURATION_STANDARD);
     }
 
-    private boolean shouldShowPriorityOnboarding() {
-        return !Prefs.getBoolean(mUserContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, false);
-    }
-
-    private void showPriorityOnboarding() {
-        View onboardingView = LayoutInflater.from(mContext)
-                .inflate(R.layout.priority_onboarding_half_shell, null);
-
-        boolean ignoreDnd = false;
+    private boolean willBypassDnd() {
+        boolean bypassesDnd = false;
         try {
-            ignoreDnd = mINotificationManager
-                    .getConsolidatedNotificationPolicy().priorityConversationSenders ==
-                    NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
+            int allowedSenders = mINotificationManager
+                    .getConsolidatedNotificationPolicy().priorityConversationSenders;
+            bypassesDnd =  allowedSenders == CONVERSATION_SENDERS_IMPORTANT
+                    || allowedSenders == CONVERSATION_SENDERS_ANYONE;
         } catch (RemoteException e) {
             Log.e(TAG, "Could not check conversation senders", e);
         }
+        return bypassesDnd;
+    }
 
-        boolean showAsBubble = mBubbleMetadata != null
-                && mBubbleMetadata.getAutoExpandBubble()
+    private boolean willShowAsBubble() {
+        return mBubbleMetadata != null
                 && BubblesManager.areBubblesEnabled(mContext, mSbn.getUser());
-
-        Drawable person =  mIconFactory.getBaseIconDrawable(mShortcutInfo);
-        if (person == null) {
-            person = mContext.getDrawable(R.drawable.ic_person).mutate();
-            TypedArray ta = mContext.obtainStyledAttributes(new int[]{android.R.attr.colorAccent});
-            int colorAccent = ta.getColor(0, 0);
-            ta.recycle();
-            person.setTint(colorAccent);
-        }
-
-        PriorityOnboardingDialogController controller = mBuilderProvider.get()
-                .setContext(mUserContext)
-                .setView(onboardingView)
-                .setIgnoresDnd(ignoreDnd)
-                .setShowsAsBubble(showAsBubble)
-                .setIcon(person)
-                .setBadge(mIconFactory.getAppBadge(
-                        mPackageName, UserHandle.getUserId(mSbn.getUid())))
-                .setOnSettingsClick(mOnConversationSettingsClickListener)
-                .setPeopleSpaceWidgetManager(mPeopleSpaceWidgetManager)
-                .setShadeController(mShadeController)
-                .build();
-
-        controller.init(mShortcutInfo);
-        controller.show();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 1a7f5b0..59b88a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -129,7 +129,6 @@
     private final LauncherApps mLauncherApps;
     private final ShortcutManager mShortcutManager;
     private final UserContextProvider mContextTracker;
-    private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
     private final UiEventLogger mUiEventLogger;
     private final ShadeController mShadeController;
     private final AppWidgetManager mAppWidgetManager;
@@ -150,7 +149,6 @@
             ShortcutManager shortcutManager,
             ChannelEditorDialogController channelEditorDialogController,
             UserContextProvider contextTracker,
-            Provider<PriorityOnboardingDialogController.Builder> builderProvider,
             AssistantFeedbackController assistantFeedbackController,
             Optional<BubblesManager> bubblesManagerOptional,
             UiEventLogger uiEventLogger,
@@ -168,7 +166,6 @@
         mLauncherApps = launcherApps;
         mShortcutManager = shortcutManager;
         mContextTracker = contextTracker;
-        mBuilderProvider = builderProvider;
         mChannelEditorDialogController = channelEditorDialogController;
         mAssistantFeedbackController = assistantFeedbackController;
         mBubblesManagerOptional = bubblesManagerOptional;
@@ -503,7 +500,6 @@
                 onSnoozeClickListener,
                 iconFactoryLoader,
                 mContextTracker.getUserContext(),
-                mBuilderProvider,
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 mMainHandler,
                 mBgHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
deleted file mode 100644
index 270721f..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
+++ /dev/null
@@ -1,291 +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 com.android.systemui.statusbar.notification.row
-
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
-import android.app.Dialog
-import android.content.Context
-import android.content.pm.ShortcutInfo
-import android.graphics.Color
-import android.graphics.PixelFormat
-import android.graphics.drawable.ColorDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.GradientDrawable
-import android.os.Bundle
-import android.text.SpannableStringBuilder
-import android.text.style.BulletSpan
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup.LayoutParams.MATCH_PARENT
-import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
-import android.view.Window
-import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManager
-import android.view.animation.Interpolator
-import android.view.animation.PathInterpolator
-import android.widget.ImageView
-import android.widget.TextView
-import com.android.systemui.Prefs
-import com.android.systemui.R
-import com.android.systemui.animation.Interpolators.LINEAR_OUT_SLOW_IN
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager
-import com.android.systemui.statusbar.notification.row.NotificationConversationInfo.OnConversationSettingsClickListener
-import com.android.systemui.statusbar.phone.ShadeController
-import javax.inject.Inject
-
-/**
- * Controller to handle presenting the priority conversations onboarding dialog
- */
-class PriorityOnboardingDialogController @Inject constructor(
-    val view: View,
-    val context: Context,
-    private val ignoresDnd: Boolean,
-    private val showsAsBubble: Boolean,
-    val icon: Drawable,
-    private val onConversationSettingsClickListener: OnConversationSettingsClickListener,
-    val badge: Drawable,
-    private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
-    private val shadeController: ShadeController
-) {
-
-    private lateinit var dialog: Dialog
-    private lateinit var shortcutInfo: ShortcutInfo
-    private val OVERSHOOT: Interpolator = PathInterpolator(0.4f, 0f, 0.2f, 1.4f)
-    private val IMPORTANCE_ANIM_DELAY = 150L
-    private val IMPORTANCE_ANIM_GROW_DURATION = 250L
-    private val IMPORTANCE_ANIM_SHRINK_DURATION = 200L
-    private val IMPORTANCE_ANIM_SHRINK_DELAY = 25L
-
-    fun init(info: ShortcutInfo) {
-        shortcutInfo = info
-        initDialog()
-    }
-
-    fun show() {
-        dialog.show()
-    }
-
-    private fun done() {
-        // Log that the user has seen the onboarding
-        Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true)
-        dialog.dismiss()
-        shadeController.animateCollapsePanels()
-        peopleSpaceWidgetManager.requestPinAppWidget(shortcutInfo, Bundle())
-    }
-
-    private fun settings() {
-        // Log that the user has seen the onboarding
-        Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true)
-        dialog.dismiss()
-        onConversationSettingsClickListener?.onClick()
-    }
-
-    class Builder @Inject constructor() {
-        private lateinit var view: View
-        private lateinit var context: Context
-        private var ignoresDnd = false
-        private var showAsBubble = false
-        private lateinit var icon: Drawable
-        private lateinit var onConversationSettingsClickListener:
-                OnConversationSettingsClickListener
-        private lateinit var badge: Drawable
-        private lateinit var peopleSpaceWidgetManager: PeopleSpaceWidgetManager
-        private lateinit var shadeController: ShadeController
-
-        fun setView(v: View): Builder {
-            view = v
-            return this
-        }
-
-        fun setContext(c: Context): Builder {
-            context = c
-            return this
-        }
-
-        fun setIgnoresDnd(ignore: Boolean): Builder {
-            ignoresDnd = ignore
-            return this
-        }
-
-        fun setShowsAsBubble(bubble: Boolean): Builder {
-            showAsBubble = bubble
-            return this
-        }
-
-        fun setIcon(draw: Drawable): Builder {
-            icon = draw
-            return this
-        }
-        fun setBadge(badge: Drawable): Builder {
-            this.badge = badge
-            return this
-        }
-
-        fun setOnSettingsClick(onClick: OnConversationSettingsClickListener): Builder {
-            onConversationSettingsClickListener = onClick
-            return this
-        }
-
-        fun setShadeController(shadeController: ShadeController): Builder {
-            this.shadeController = shadeController
-            return this
-        }
-
-        fun setPeopleSpaceWidgetManager(peopleSpaceWidgetManager: PeopleSpaceWidgetManager):
-                Builder {
-            this.peopleSpaceWidgetManager = peopleSpaceWidgetManager
-            return this
-        }
-
-        fun build(): PriorityOnboardingDialogController {
-            val controller = PriorityOnboardingDialogController(
-                    view, context, ignoresDnd, showAsBubble, icon,
-                    onConversationSettingsClickListener, badge, peopleSpaceWidgetManager,
-                    shadeController)
-            return controller
-        }
-    }
-
-    private fun initDialog() {
-        dialog = Dialog(context)
-
-        if (dialog.window == null) {
-            throw IllegalStateException("Need a window for the onboarding dialog to show")
-        }
-
-        dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
-        // Prevent a11y readers from reading the first element in the dialog twice
-        dialog.setTitle("\u00A0")
-        dialog.apply {
-            setContentView(view)
-            setCanceledOnTouchOutside(true)
-
-            findViewById<TextView>(R.id.done_button)?.setOnClickListener {
-                done()
-            }
-
-            findViewById<TextView>(R.id.settings_button)?.setOnClickListener {
-                settings()
-            }
-
-            findViewById<ImageView>(R.id.conversation_icon)?.setImageDrawable(icon)
-            findViewById<ImageView>(R.id.icon)?.setImageDrawable(badge)
-            val mImportanceRingView = findViewById<ImageView>(R.id.conversation_icon_badge_ring)
-            val conversationIconBadgeBg = findViewById<ImageView>(R.id.conversation_icon_badge_bg)
-
-            val ring: GradientDrawable = mImportanceRingView.drawable as GradientDrawable
-            ring.mutate()
-            val bg = conversationIconBadgeBg.drawable as GradientDrawable
-            bg.mutate()
-            val ringColor = context.getResources()
-                    .getColor(com.android.internal.R.color.conversation_important_highlight)
-            val standardThickness = context.resources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.importance_ring_stroke_width)
-            val largeThickness = context.resources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.importance_ring_anim_max_stroke_width)
-            val standardSize = context.resources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.importance_ring_size)
-            val baseSize = standardSize - standardThickness * 2
-            val largeSize = baseSize + largeThickness * 2
-            val bgSize = context.resources.getDimensionPixelSize(
-                    com.android.internal.R.dimen.conversation_icon_size_badged)
-
-            val animatorUpdateListener: ValueAnimator.AnimatorUpdateListener =
-                    ValueAnimator.AnimatorUpdateListener { animation ->
-                val strokeWidth = animation.animatedValue as Int
-                ring.setStroke(strokeWidth, ringColor)
-                val newSize = baseSize + strokeWidth * 2
-                ring.setSize(newSize, newSize)
-                mImportanceRingView.invalidate()
-            }
-
-            val growAnimation: ValueAnimator = ValueAnimator.ofInt(0, largeThickness)
-            growAnimation.interpolator = LINEAR_OUT_SLOW_IN
-            growAnimation.duration = IMPORTANCE_ANIM_GROW_DURATION
-            growAnimation.addUpdateListener(animatorUpdateListener)
-
-            val shrinkAnimation: ValueAnimator =
-                    ValueAnimator.ofInt(largeThickness, standardThickness)
-            shrinkAnimation.duration = IMPORTANCE_ANIM_SHRINK_DURATION
-            shrinkAnimation.startDelay = IMPORTANCE_ANIM_SHRINK_DELAY
-            shrinkAnimation.interpolator = OVERSHOOT
-            shrinkAnimation.addUpdateListener(animatorUpdateListener)
-            shrinkAnimation.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?) {
-                    // Shrink the badge bg so that it doesn't peek behind the animation
-                    bg.setSize(baseSize, baseSize)
-                    conversationIconBadgeBg.invalidate()
-                }
-
-                override fun onAnimationEnd(animation: Animator?) {
-                    // Reset bg back to normal size
-                    bg.setSize(bgSize, bgSize)
-                    conversationIconBadgeBg.invalidate()
-                }
-            })
-
-            val anims = AnimatorSet()
-            anims.startDelay = IMPORTANCE_ANIM_DELAY
-            anims.playSequentially(growAnimation, shrinkAnimation)
-
-            val gapWidth = dialog.context.getResources().getDimensionPixelSize(
-                    R.dimen.conversation_onboarding_bullet_gap_width)
-            val description = SpannableStringBuilder()
-            description.append(context.getText(R.string.priority_onboarding_show_at_top_text),
-                    BulletSpan(gapWidth), /* flags */0)
-            description.append(System.lineSeparator())
-            description.append(context.getText(R.string.priority_onboarding_show_avatar_text),
-                    BulletSpan(gapWidth), /* flags */0)
-            if (showsAsBubble) {
-                description.append(System.lineSeparator())
-                description.append(context.getText(
-                        R.string.priority_onboarding_appear_as_bubble_text),
-                        BulletSpan(gapWidth), /* flags */0)
-            }
-            if (ignoresDnd) {
-                description.append(System.lineSeparator())
-                description.append(context.getText(R.string.priority_onboarding_ignores_dnd_text),
-                        BulletSpan(gapWidth), /* flags */0)
-            }
-            findViewById<TextView>(R.id.behaviors).setText(description)
-
-            window?.apply {
-                setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
-                addFlags(wmFlags)
-                setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
-                setWindowAnimations(com.android.internal.R.style.Animation_InputMethod)
-
-                attributes = attributes.apply {
-                    format = PixelFormat.TRANSLUCENT
-                    title = PriorityOnboardingDialogController::class.java.simpleName
-                    gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
-                    fitInsetsTypes = attributes.fitInsetsTypes and statusBars().inv()
-                    width = MATCH_PARENT
-                    height = WRAP_CONTENT
-                }
-            }
-            anims.start()
-        }
-    }
-
-    private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
-            or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-            or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
-}
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..56c0bd5 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();
     }
@@ -2075,20 +2073,18 @@
         setQsExpansionEnabled(mAmbientState.getScrollY() == 0);
 
         int radius = mScrimCornerRadius;
-        if (visible || !mShouldUseSplitNotificationShade) {
-            if (!mShouldUseSplitNotificationShade) {
-                top = (int) Math.min(qsPanelBottomY, notificationTop);
-                bottom = getView().getBottom();
-                left = getView().getLeft();
-                right = getView().getRight();
-                radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius,
-                        Math.min(top / (float) mScrimCornerRadius, 1f));
-            } else {
-                top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
-                bottom = mNotificationStackScrollLayoutController.getHeight();
-                left = mNotificationStackScrollLayoutController.getLeft();
-                right = mNotificationStackScrollLayoutController.getRight();
-            }
+        if (!mShouldUseSplitNotificationShade) {
+            top = (int) Math.min(qsPanelBottomY, notificationTop);
+            bottom = getView().getBottom();
+            left = getView().getLeft();
+            right = getView().getRight();
+            radius = (int) MathUtils.lerp(mScreenCornerRadius, mScrimCornerRadius,
+                    Math.min(top / (float) mScrimCornerRadius, 1f));
+        } else if (qsPanelBottomY > 0) { // so bounds are empty on lockscreen
+            top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
+            bottom = mNotificationStackScrollLayoutController.getHeight();
+            left = mNotificationStackScrollLayoutController.getLeft();
+            right = mNotificationStackScrollLayoutController.getRight();
         }
 
         // Fancy clipping for quick settings
@@ -2698,6 +2694,7 @@
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
         mIsExpanding = false;
         mMediaHierarchyManager.setCollapsingShadeFromQS(false);
+        mMediaHierarchyManager.setQsExpanded(mQsExpanded);
         if (isFullyCollapsed()) {
             DejankUtils.postAfterTraversal(new Runnable() {
                 @Override
@@ -3709,6 +3706,12 @@
 
         @Override
         public void flingTopOverscroll(float velocity, boolean open) {
+            // in split shade mode we want to expand/collapse QS only when touch happens within QS
+            if (mShouldUseSplitNotificationShade
+                    && (mInitialTouchX < mQsFrame.getX()
+                        || mInitialTouchX > mQsFrame.getX() + mQsFrame.getWidth())) {
+                return;
+            }
             mLastOverscroll = 0f;
             mQsExpansionFromOverscroll = false;
             setQsExpansion(mQsExpansionHeight);
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/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/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/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
index cc322620..0dd1f68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -59,6 +58,7 @@
 import com.android.internal.appwidget.IAppWidgetService;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.people.widget.PeopleTileKey;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -73,6 +73,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
@@ -188,6 +189,8 @@
     private PackageManager mPackageManager;
     @Mock
     private NotificationEntryManager mNotificationEntryManager;
+    @Mock
+    private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
 
     private Bundle mOptions;
 
@@ -212,8 +215,8 @@
         when(resources.getConfiguration()).thenReturn(configuration);
         when(resources.getDisplayMetrics()).thenReturn(displayMetrics);
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
-        when(mMockContentResolver.query(any(Uri.class), any(), anyString(), any(),
-                isNull())).thenReturn(mMockCursor);
+        when(mMockContentResolver.query(any(Uri.class), any(), any(), any(),
+                any())).thenReturn(mMockCursor);
         when(mMockContext.getString(R.string.birthday_status)).thenReturn(
                 mContext.getString(R.string.birthday_status));
         when(mMockContext.getString(R.string.basic_status)).thenReturn(
@@ -236,7 +239,8 @@
                         .build();
         PeopleTileKey key = new PeopleTileKey(tile);
         PeopleSpaceTile actual = PeopleSpaceUtils
-                .augmentTileFromNotification(mContext, tile, key, mNotificationEntry1, 0);
+                .augmentTileFromNotification(mContext, tile, key, mNotificationEntry1, 0,
+                        Optional.empty());
 
         assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
         assertThat(actual.getNotificationSender()).isEqualTo(null);
@@ -275,7 +279,8 @@
                         .build();
         PeopleTileKey key = new PeopleTileKey(tile);
         PeopleSpaceTile actual = PeopleSpaceUtils
-                .augmentTileFromNotification(mContext, tile, key, notificationEntry, 0);
+                .augmentTileFromNotification(mContext, tile, key, notificationEntry, 0,
+                        Optional.empty());
 
         assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2);
         assertThat(actual.getNotificationSender().toString()).isEqualTo("name");
@@ -291,7 +296,8 @@
                         .build();
         PeopleTileKey key = new PeopleTileKey(tile);
         PeopleSpaceTile actual = PeopleSpaceUtils
-                .augmentTileFromNotification(mContext, tile, key, mNotificationEntry3, 0);
+                .augmentTileFromNotification(mContext, tile, key, mNotificationEntry3, 0,
+                        Optional.empty());
 
         assertThat(actual.getNotificationContent()).isEqualTo(null);
     }
@@ -308,10 +314,11 @@
         Map<Integer, PeopleSpaceTile> widgetIdToTile = Map.of(WIDGET_ID_WITH_SHORTCUT,
                 new PeopleSpaceTile.Builder(mShortcutInfoWithoutPerson,
                         mContext.getSystemService(LauncherApps.class)).build());
-        PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+        PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
                 widgetIdToTile, widgetIdsArray);
 
-        verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+        verify(mPeopleSpaceWidgetManager, never()).updateAppWidgetOptionsAndView(
+                eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
 
@@ -328,10 +335,11 @@
                 new PeopleSpaceTile.Builder(mShortcutInfoWithoutPerson,
                         mContext.getSystemService(LauncherApps.class)).setBirthdayText(
                         mContext.getString(R.string.birthday_status)).build());
-        PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+        PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
                 widgetIdToTile, widgetIdsArray);
 
-        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+        verify(mPeopleSpaceWidgetManager, times(1)).updateAppWidgetOptionsAndView(
+                eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
 
@@ -363,10 +371,11 @@
                 new PeopleSpaceTile.Builder(mShortcutInfo,
                         mContext.getSystemService(LauncherApps.class)).setBirthdayText(
                         mContext.getString(R.string.birthday_status)).build());
-        PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+        PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
                 widgetIdToTile, widgetIdsArray);
 
-        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+        verify(mPeopleSpaceWidgetManager, times(1)).updateAppWidgetOptionsAndView(
+                eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
 
@@ -375,6 +384,9 @@
         int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT};
         when(mMockCursor.moveToNext()).thenReturn(true, false, true, false);
         when(mMockCursor.getString(eq(TEST_COLUMN_INDEX))).thenReturn(TEST_LOOKUP_KEY);
+        when(mMockCursor.getInt(eq(TEST_COLUMN_INDEX + 1))).thenReturn(1);
+        when(mMockCursor.getColumnIndex(eq(ContactsContract.Contacts.STARRED))).thenReturn(
+                TEST_COLUMN_INDEX + 1);
         when(mMockCursor.getColumnIndex(eq(ContactsContract.CommonDataKinds.Event.LOOKUP_KEY)
         )).thenReturn(TEST_COLUMN_INDEX);
 
@@ -383,10 +395,11 @@
                 new PeopleSpaceTile.Builder(mShortcutInfo,
                         mContext.getSystemService(LauncherApps.class)).setBirthdayText(
                         mContext.getString(R.string.birthday_status)).build());
-        PeopleSpaceUtils.getBirthdays(mMockContext, mAppWidgetManager,
+        PeopleSpaceUtils.getDataFromContacts(mMockContext, mPeopleSpaceWidgetManager,
                 widgetIdToTile, widgetIdsArray);
 
-        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+        verify(mPeopleSpaceWidgetManager, times(1)).updateAppWidgetOptionsAndView(
+                eq(WIDGET_ID_WITH_SHORTCUT),
                 any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index 764cdee..228e5e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -21,13 +21,20 @@
 import static android.app.people.ConversationStatus.ACTIVITY_GAME;
 import static android.app.people.ConversationStatus.ACTIVITY_NEW_STORY;
 import static android.app.people.ConversationStatus.AVAILABILITY_AVAILABLE;
+import static android.app.people.PeopleSpaceTile.BLOCK_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_CONTACTS;
+import static android.app.people.PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_STARRED_CONTACTS;
 import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
 
+import static com.android.systemui.people.PeopleSpaceUtils.STARRED_CONTACT;
+import static com.android.systemui.people.PeopleSpaceUtils.VALID_CONTACT;
 import static com.android.systemui.people.widget.AppWidgetOptionsHelper.OPTIONS_PEOPLE_TILE;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -53,6 +60,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.people.widget.PeopleTileKey;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -148,14 +156,14 @@
         TextView textView = mock(TextView.class);
         when(textView.getLineHeight()).thenReturn(16);
         when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null);
-        mPeopleTileViewHelper = new PeopleTileViewHelper(mContext,
-                PERSON_TILE, 0, mOptions);
+        mPeopleTileViewHelper = getPeopleTileViewHelper(
+                PERSON_TILE, mOptions);
     }
 
     @Test
     public void testCreateRemoteViewsWithLastInteractionTimeUnderOneDayHidden() {
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                PERSON_TILE_WITHOUT_NOTIFICATION, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         // Not showing last interaction.
@@ -165,8 +173,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                PERSON_TILE_WITHOUT_NOTIFICATION, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         // Not showing last interaction.
@@ -178,8 +186,8 @@
         PeopleSpaceTile tileWithLastInteraction =
                 PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setLastInteractionTimestamp(
                         123445L).build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithLastInteraction, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithLastInteraction, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -197,8 +205,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithLastInteraction, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithLastInteraction, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show name over predefined icon.
@@ -214,8 +222,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithLastInteraction, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithLastInteraction, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -240,8 +248,8 @@
                                 new ConversationStatus.Builder(
                                         PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
                                         ACTIVITY_GAME).build())).build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithAvailabilityAndNewStory, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -257,8 +265,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithAvailabilityAndNewStory, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show name rather than game type.
@@ -274,8 +282,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithAvailabilityAndNewStory, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithAvailabilityAndNewStory, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -298,8 +306,8 @@
                                 NEW_STORY_WITH_AVAILABILITY, new ConversationStatus.Builder(
                                         PERSON_TILE_WITHOUT_NOTIFICATION.getId(),
                                         ACTIVITY_BIRTHDAY).build())).build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithStatusTemplate, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithStatusTemplate, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -318,8 +326,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithStatusTemplate, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithStatusTemplate, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show icon instead of name.
@@ -336,8 +344,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithStatusTemplate, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithStatusTemplate, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -362,8 +370,8 @@
                 PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setStatuses(
                         Arrays.asList(GAME_STATUS,
                                 NEW_STORY_WITH_AVAILABILITY)).build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithStatusTemplate, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithStatusTemplate, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -381,8 +389,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithStatusTemplate, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithStatusTemplate, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show icon instead of name.
@@ -399,8 +407,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithStatusTemplate, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithStatusTemplate, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -420,14 +428,128 @@
     }
 
     @Test
+    public void testCreateRemoteViewsWithPackageSuspended() {
+        PeopleSpaceTile tile = PERSON_TILE.toBuilder()
+                .setIsPackageSuspended(true)
+                .build();
+        RemoteViews views = getPeopleTileViewHelper(
+                tile, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithUserQuieted() {
+        PeopleSpaceTile tile = PERSON_TILE.toBuilder()
+                .setIsUserQuieted(true)
+                .build();
+        RemoteViews views = getPeopleTileViewHelper(
+                tile, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+    }
+
+    @Test
+    public void testCreateRemoteViewsWithDndBlocking() {
+        PeopleSpaceTile tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(BLOCK_CONVERSATIONS)
+                .build();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        View result = views.apply(mContext, null);
+
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(BLOCK_CONVERSATIONS)
+                .setCanBypassDnd(true)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(SHOW_IMPORTANT_CONVERSATIONS)
+                .setIsImportantConversation(true)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(SHOW_STARRED_CONTACTS)
+                .setContactAffinity(VALID_CONTACT)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(SHOW_STARRED_CONTACTS)
+                .setContactAffinity(STARRED_CONTACT)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(SHOW_CONTACTS)
+                .setContactAffinity(STARRED_CONTACT)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(SHOW_CONTACTS)
+                .setContactAffinity(VALID_CONTACT)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertNotEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+
+        tileWithDndBlocking = PERSON_TILE.toBuilder()
+                .setNotificationPolicyState(SHOW_CONTACTS)
+                .build();
+        views = getPeopleTileViewHelper(
+                tileWithDndBlocking, mOptions).getViews();
+        result = views.apply(mContext, null);
+
+        assertEquals(result.getSourceLayoutResId(), R.layout.people_tile_empty_layout);
+    }
+
+    @Test
     public void testCreateRemoteViewsWithMissedCallNotification() {
         PeopleSpaceTile tileWithMissedCallNotification = PERSON_TILE.toBuilder()
                 .setNotificationDataUri(null)
                 .setNotificationCategory(CATEGORY_MISSED_CALL)
                 .setNotificationContent(MISSED_CALL)
                 .build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithMissedCallNotification, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithMissedCallNotification, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -446,8 +568,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithMissedCallNotification, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithMissedCallNotification, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show icon instead of name.
@@ -463,8 +585,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithMissedCallNotification, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithMissedCallNotification, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -489,8 +611,8 @@
                 .setNotificationDataUri(null)
                 .setStatuses(Arrays.asList(GAME_STATUS,
                         NEW_STORY_WITH_AVAILABILITY)).build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -512,8 +634,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show icon instead of name.
@@ -531,8 +653,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -561,8 +683,8 @@
                 .setNotificationDataUri(null)
                 .setStatuses(Arrays.asList(GAME_STATUS,
                         NEW_STORY_WITH_AVAILABILITY)).build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -588,8 +710,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show icon instead of name.
@@ -607,8 +729,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -642,8 +764,8 @@
                 .setStatuses(Arrays.asList(GAME_STATUS,
                         NEW_STORY_WITH_AVAILABILITY))
                 .setMessagesCount(2).build();
-        RemoteViews views = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews views = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View result = views.apply(mContext, null);
 
         TextView name = (TextView) result.findViewById(R.id.name);
@@ -665,8 +787,8 @@
 
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_width_for_medium) - 1);
-        RemoteViews smallView = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews smallView = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View smallResult = smallView.apply(mContext, null);
 
         // Show icon instead of name.
@@ -684,8 +806,8 @@
                 getSizeInDp(R.dimen.required_width_for_large));
         mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
                 getSizeInDp(R.dimen.required_height_for_large));
-        RemoteViews largeView = new PeopleTileViewHelper(mContext,
-                tileWithStatusAndNotification, 0, mOptions).getViews();
+        RemoteViews largeView = getPeopleTileViewHelper(
+                tileWithStatusAndNotification, mOptions).getViews();
         View largeResult = largeView.apply(mContext, null);
 
         name = (TextView) largeResult.findViewById(R.id.name);
@@ -858,4 +980,9 @@
         return (int) (mContext.getResources().getDimension(dimenResourceId)
                 / mContext.getResources().getDisplayMetrics().density);
     }
+
+    private PeopleTileViewHelper getPeopleTileViewHelper(PeopleSpaceTile tile, Bundle options) {
+        return new PeopleTileViewHelper(mContext, tile, 0, options,
+                new PeopleTileKey(tile.getId(), 0, tile.getPackageName()));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
index 411fb02..f31f326 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java
@@ -20,11 +20,32 @@
 import static android.app.Notification.EXTRA_PEOPLE_LIST;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
+import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
 import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
 import static android.app.people.ConversationStatus.ACTIVITY_BIRTHDAY;
 import static android.app.people.ConversationStatus.ACTIVITY_GAME;
+import static android.app.people.PeopleSpaceTile.BLOCK_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_CONTACTS;
+import static android.app.people.PeopleSpaceTile.SHOW_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
+import static android.app.people.PeopleSpaceTile.SHOW_STARRED_CONTACTS;
+import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.content.Intent.ACTION_PACKAGES_SUSPENDED;
 import static android.content.PermissionChecker.PERMISSION_GRANTED;
 import static android.content.PermissionChecker.PERMISSION_HARD_DENIED;
+import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
 
 import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
 import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
@@ -73,6 +94,7 @@
 import android.os.UserManager;
 import android.service.notification.ConversationChannelWrapper;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
 
 import androidx.preference.PreferenceManager;
@@ -89,6 +111,7 @@
 import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -99,12 +122,14 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -158,6 +183,16 @@
                     // Same contact uri.
                     .setContactUri(URI)
                     .build();
+    private static final int ALL_SUPPRESSED_VISUAL_EFFECTS = SUPPRESSED_EFFECT_SCREEN_OFF
+            | SUPPRESSED_EFFECT_SCREEN_ON
+            | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
+            | SUPPRESSED_EFFECT_AMBIENT
+            | SUPPRESSED_EFFECT_STATUS_BAR
+            | SUPPRESSED_EFFECT_BADGE
+            | SUPPRESSED_EFFECT_LIGHTS
+            | SUPPRESSED_EFFECT_PEEK
+            | SUPPRESSED_EFFECT_NOTIFICATION_LIST;
+
     private ShortcutInfo mShortcutInfo;
     private NotificationEntry mNotificationEntry;
 
@@ -182,9 +217,13 @@
     @Mock
     private PackageManager mPackageManager;
     @Mock
-    private INotificationManager mNotificationManager;
+    private INotificationManager mINotificationManager;
     @Mock
     private UserManager mUserManager;
+    @Mock
+    private NotificationManager mNotificationManager;
+    @Mock
+    private NotificationManager.Policy mNotificationPolicy;
 
     @Captor
     private ArgumentCaptor<NotificationHandler> mListenerCaptor;
@@ -194,19 +233,16 @@
     private final NoManSimulator mNoMan = new NoManSimulator();
     private final FakeSystemClock mClock = new FakeSystemClock();
 
-    private PeopleSpaceWidgetProvider mProvider;
+    private final FakeExecutor mFakeExecutor = new FakeExecutor(mClock);
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mLauncherApps = mock(LauncherApps.class);
         mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
-        mManager = new PeopleSpaceWidgetManager(mContext);
-        mProvider = new PeopleSpaceWidgetProvider();
-        mProvider.setPeopleSpaceWidgetManager(mManager);
-        mManager.setAppWidgetManager(mAppWidgetManager, mIPeopleManager, mPeopleManager,
-                mLauncherApps, mNotificationEntryManager, mPackageManager, true, mProvider,
-                mUserManager, mNotificationManager);
+        mManager = new PeopleSpaceWidgetManager(mContext, mAppWidgetManager, mIPeopleManager,
+                mPeopleManager, mLauncherApps, mNotificationEntryManager, mPackageManager,
+                mUserManager, mINotificationManager, mNotificationManager, mFakeExecutor);
         mManager.attach(mListenerService);
 
         verify(mListenerService).addNotificationHandler(mListenerCaptor.capture());
@@ -218,7 +254,19 @@
         addTileForWidget(PERSON_TILE_WITH_SAME_URI, WIDGET_ID_WITH_SAME_URI);
         when(mAppWidgetManager.getAppWidgetOptions(eq(WIDGET_ID_WITHOUT_SHORTCUT)))
                 .thenReturn(new Bundle());
+
         when(mUserManager.isQuietModeEnabled(any())).thenReturn(false);
+        when(mPackageManager.isPackageSuspended(any())).thenReturn(false);
+        setFinalField("suppressedVisualEffects", ALL_SUPPRESSED_VISUAL_EFFECTS);
+        when(mNotificationPolicy.allowConversationsFrom()).thenReturn(CONVERSATION_SENDERS_ANYONE);
+        when(mNotificationPolicy.allowConversations()).thenReturn(false);
+        when(mNotificationPolicy.allowMessagesFrom()).thenReturn(ZenModeConfig.SOURCE_ANYONE);
+        when(mNotificationPolicy.allowMessages()).thenReturn(false);
+        when(mNotificationManager.getNotificationPolicy()).thenReturn(mNotificationPolicy);
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_ALL);
+        int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT};
+        when(mAppWidgetManager.getAppWidgetIds(any())).thenReturn(widgetIdsArray);
 
         when(mMockContext.getPackageName()).thenReturn(TEST_PACKAGE_A);
         when(mMockContext.getUserId()).thenReturn(0);
@@ -242,7 +290,7 @@
         ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
                 SHORTCUT_ID + 2,
                 true, 1);
-        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+        when(mINotificationManager.getConversations(anyBoolean())).thenReturn(
                 new ParceledListSlice(Arrays.asList(
                         newerNonImportantConversation, newerImportantConversation,
                         olderImportantConversation)));
@@ -280,7 +328,7 @@
         ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
                 SHORTCUT_ID + 2,
                 true, 1);
-        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+        when(mINotificationManager.getConversations(anyBoolean())).thenReturn(
                 new ParceledListSlice(Arrays.asList(
                         newerNonImportantConversation, newerImportantConversation,
                         olderImportantConversation)));
@@ -306,7 +354,7 @@
         ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper(
                 SHORTCUT_ID + 2,
                 true, 1);
-        when(mNotificationManager.getConversations(anyBoolean())).thenReturn(
+        when(mINotificationManager.getConversations(anyBoolean())).thenReturn(
                 new ParceledListSlice(Arrays.asList(
                         newerNonImportantConversation, newerImportantConversation,
                         olderImportantConversation)));
@@ -1027,8 +1075,7 @@
     public void testDeleteAllWidgetsForConversationsUncachesShortcutAndRemovesListeners()
             throws Exception {
         addSecondWidgetForPersonTile();
-        mProvider.onUpdate(mContext, mAppWidgetManager,
-                new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT});
+        mManager.updateWidgets(new int[]{WIDGET_ID_WITH_SHORTCUT, SECOND_WIDGET_ID_WITH_SHORTCUT});
 
         // Delete only one widget for the conversation.
         mManager.deleteWidgets(new int[]{WIDGET_ID_WITH_SHORTCUT});
@@ -1050,7 +1097,7 @@
                 eq(LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS));
 
         // Delete all widgets for the conversation.
-        mProvider.onDeleted(mContext, new int[]{SECOND_WIDGET_ID_WITH_SHORTCUT});
+        mManager.deleteWidgets(new int[]{SECOND_WIDGET_ID_WITH_SHORTCUT});
 
         // Check deleted storage.
         SharedPreferences secondWidgetSp = mContext.getSharedPreferences(
@@ -1154,7 +1201,7 @@
                 new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A));
         when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(channel);
         PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A);
-        PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key);
+        PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile.getId()).isEqualTo(key.getShortcutId());
     }
 
@@ -1162,7 +1209,7 @@
     public void testGetPeopleTileFromPersistentStorageNoConversation() throws RemoteException {
         when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(null);
         PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A);
-        PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key);
+        PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT);
         assertThat(tile).isNull();
     }
 
@@ -1195,18 +1242,25 @@
 
     @Test
     public void testAugmentTileFromNotifications() {
+        clearStorage();
+        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
+        assertThat(sp.getString(String.valueOf(WIDGET_ID_WITH_SHORTCUT), null)).isEqualTo(null);
         PeopleSpaceTile tile =
                 new PeopleSpaceTile
                         .Builder(SHORTCUT_ID, "userName", ICON, new Intent())
                         .setPackageName(TEST_PACKAGE_A)
                         .setUserHandle(new UserHandle(0))
                         .build();
+
         PeopleTileKey key = new PeopleTileKey(tile);
         PeopleSpaceTile actual = mManager.augmentTileFromNotifications(tile, key, EMPTY_STRING,
-                        Map.of(new PeopleTileKey(mNotificationEntry),
-                                new HashSet<>(Collections.singleton(mNotificationEntry))));
+                Map.of(new PeopleTileKey(mNotificationEntry),
+                        new HashSet<>(Collections.singleton(mNotificationEntry))),
+                Optional.of(WIDGET_ID_WITH_SHORTCUT));
 
         assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_CONTENT_1);
+        assertThat(sp.getString(String.valueOf(WIDGET_ID_WITH_SHORTCUT), null)).isEqualTo(
+                URI.toString());
     }
 
     @Test
@@ -1221,7 +1275,8 @@
         PeopleSpaceTile actual = mManager
                 .augmentTileFromNotifications(tile, key, EMPTY_STRING,
                         Map.of(new PeopleTileKey(mNotificationEntry),
-                                new HashSet<>(Collections.singleton(mNotificationEntry))));
+                                new HashSet<>(Collections.singleton(mNotificationEntry))),
+                        Optional.empty());
 
         assertThat(actual.getNotificationContent()).isEqualTo(null);
     }
@@ -1238,7 +1293,8 @@
                 .thenReturn(List.of(mNotificationEntry));
 
         PeopleSpaceTile actual =
-                mManager.augmentTileFromNotificationEntryManager(tile);
+                mManager.augmentTileFromNotificationEntryManager(tile,
+                        Optional.of(WIDGET_ID_WITH_SHORTCUT));
 
         assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_CONTENT_1);
 
@@ -1246,6 +1302,202 @@
                 .getVisibleNotifications();
     }
 
+    @Test
+    public void testUpdateWidgetsOnStateChange() {
+        mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        Bundle bundle = mBundleArgumentCaptor.getValue();
+        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.isPackageSuspended()).isFalse();
+        assertThat(tile.isUserQuieted()).isFalse();
+        assertThat(tile.canBypassDnd()).isFalse();
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+        verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT),
+                any());
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeWithUserQuieted() {
+        when(mUserManager.isQuietModeEnabled(any())).thenReturn(true);
+
+        mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        Bundle bundle = mBundleArgumentCaptor.getValue();
+        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.isPackageSuspended()).isFalse();
+        assertThat(tile.isUserQuieted()).isTrue();
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeWithPackageSuspended() throws Exception {
+        when(mPackageManager.isPackageSuspended(any())).thenReturn(true);
+
+        mManager.updateWidgetsOnStateChange(ACTION_PACKAGES_SUSPENDED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        Bundle bundle = mBundleArgumentCaptor.getValue();
+        PeopleSpaceTile tile = bundle.getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.isPackageSuspended()).isTrue();
+        assertThat(tile.isUserQuieted()).isFalse();
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(SHOW_CONVERSATIONS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeNotInDnd() {
+        int expected = 0;
+        mManager.updateWidgetsOnStateChange(NotificationManager
+                .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeAllConversations() {
+        int expected = 0;
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_PRIORITY);
+        when(mNotificationPolicy.allowConversations()).thenReturn(true);
+        setFinalField("priorityConversationSenders", CONVERSATION_SENDERS_ANYONE);
+
+        mManager.updateWidgetsOnStateChange(NotificationManager
+                .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeAllowOnlyImportantConversations() {
+        int expected = 0;
+        // Only allow important conversations.
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_PRIORITY);
+        when(mNotificationPolicy.allowConversations()).thenReturn(true);
+        setFinalField("priorityConversationSenders", CONVERSATION_SENDERS_IMPORTANT);
+
+        mManager.updateWidgetsOnStateChange(NotificationManager
+                .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(
+                expected | SHOW_IMPORTANT_CONVERSATIONS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeAllowNoConversations() {
+        int expected = 0;
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_PRIORITY);
+        when(mNotificationPolicy.allowConversations()).thenReturn(false);
+
+        mManager.updateWidgetsOnStateChange(NotificationManager
+                .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | BLOCK_CONVERSATIONS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeAllowNoConversationsAllowContactMessages() {
+        int expected = 0;
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_PRIORITY);
+        when(mNotificationPolicy.allowConversations()).thenReturn(false);
+        when(mNotificationPolicy.allowMessagesFrom()).thenReturn(ZenModeConfig.SOURCE_CONTACT);
+        when(mNotificationPolicy.allowMessages()).thenReturn(true);
+
+        mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONTACTS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeAllowNoConversationsAllowStarredContactMessages() {
+        int expected = 0;
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_PRIORITY);
+        when(mNotificationPolicy.allowConversations()).thenReturn(false);
+        when(mNotificationPolicy.allowMessagesFrom()).thenReturn(ZenModeConfig.SOURCE_STAR);
+        when(mNotificationPolicy.allowMessages()).thenReturn(true);
+
+        mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_STARRED_CONTACTS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeAllowAlarmsOnly() {
+        int expected = 0;
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_ALARMS);
+
+        mManager.updateWidgetsOnStateChange(NotificationManager
+                .ACTION_INTERRUPTION_FILTER_CHANGED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | BLOCK_CONVERSATIONS);
+    }
+
+    @Test
+    public void testUpdateWidgetsOnStateChangeAllowVisualEffectsAndAllowAlarmsOnly() {
+        int expected = 0;
+        // If we show visuals, but just only make sounds for alarms, still show content in tiles.
+        when(mNotificationManager.getCurrentInterruptionFilter()).thenReturn(
+                INTERRUPTION_FILTER_ALARMS);
+        setFinalField("suppressedVisualEffects", SUPPRESSED_EFFECT_FULL_SCREEN_INTENT
+                | SUPPRESSED_EFFECT_AMBIENT);
+
+        mManager.updateWidgetsOnStateChange(ACTION_BOOT_COMPLETED);
+
+        verify(mAppWidgetManager, times(1))
+                .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT),
+                        mBundleArgumentCaptor.capture());
+        PeopleSpaceTile tile = mBundleArgumentCaptor.getValue().getParcelable(OPTIONS_PEOPLE_TILE);
+        assertThat(tile.getNotificationPolicyState()).isEqualTo(expected | SHOW_CONVERSATIONS);
+    }
+
+    private void setFinalField(String fieldName, int value) {
+        try {
+            Field field = NotificationManager.Policy.class.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            field.set(mNotificationPolicy, value);
+        } catch (Exception e) {
+        }
+    }
+
     /**
      * Adds another widget for {@code PERSON_TILE} with widget ID: {@code
      * SECOND_WIDGET_ID_WITH_SHORTCUT}.
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/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 5d29f52..e85e19f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -21,6 +21,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
 import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
@@ -150,9 +152,6 @@
     private ShadeController mShadeController;
     @Mock
     private ConversationIconFactory mIconFactory;
-    @Mock(answer = Answers.RETURNS_SELF)
-    private PriorityOnboardingDialogController.Builder mBuilder;
-    private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider = () -> mBuilder;
     @Mock
     private Notification.BubbleMetadata mBubbleMetadata;
     private Handler mTestHandler;
@@ -236,8 +235,6 @@
         when(mMockINotificationManager.getConsolidatedNotificationPolicy())
                 .thenReturn(mock(NotificationManager.Policy.class));
 
-        when(mBuilder.build()).thenReturn(mock(PriorityOnboardingDialogController.class));
-
         when(mPeopleSpaceWidgetManager.requestPinAppWidget(any(), any())).thenReturn(true);
     }
 
@@ -258,7 +255,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -285,7 +281,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -340,7 +335,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -368,7 +362,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -395,7 +388,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -433,7 +425,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -464,7 +455,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -493,7 +483,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -523,7 +512,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 false,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -551,7 +539,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -582,7 +569,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -616,7 +602,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -626,6 +611,110 @@
         assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
                 mContext.getString(R.string.notification_channel_summary_default_with_bubbles,
                         "App Name"));
+        assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+                .isEqualTo(mContext.getString(
+                        R.string.notification_channel_summary_priority_bubble));
+    }
+
+    @Test
+    public void testBindNotification_priorityDnd() throws Exception {
+        NotificationManager.Policy policy = new NotificationManager.Policy(
+                PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_ANYONE);
+        when(mMockINotificationManager.getConsolidatedNotificationPolicy())
+                .thenReturn(policy);
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+        mConversationChannel.setImportance(IMPORTANCE_HIGH);
+        mConversationChannel.setImportantConversation(false);
+        mConversationChannel.setAllowBubbles(false);
+        mNotificationInfo.bindNotification(
+                -1,
+                mShortcutManager,
+                mMockPackageManager,
+                mPeopleSpaceWidgetManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                mIconFactory,
+                mContext,
+                true,
+                mTestHandler,
+                mTestHandler, null, Optional.of(mBubblesManager),
+                mShadeController);
+        assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+                .isEqualTo(mContext.getString(
+                        R.string.notification_channel_summary_priority_dnd));
+    }
+
+    @Test
+    public void testBindNotification_priorityBaseline() throws Exception {
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+        mConversationChannel.setImportance(IMPORTANCE_HIGH);
+        mConversationChannel.setImportantConversation(false);
+        mConversationChannel.setAllowBubbles(false);
+        mNotificationInfo.bindNotification(
+                -1,
+                mShortcutManager,
+                mMockPackageManager,
+                mPeopleSpaceWidgetManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                mIconFactory,
+                mContext,
+                true,
+                mTestHandler,
+                mTestHandler, null, Optional.of(mBubblesManager),
+                mShadeController);
+        assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+                .isEqualTo(mContext.getString(
+                        R.string.notification_channel_summary_priority_baseline));
+    }
+
+    @Test
+    public void testBindNotification_priorityDndAndBubble() throws Exception {
+        NotificationManager.Policy policy = new NotificationManager.Policy(
+                PRIORITY_CATEGORY_CONVERSATIONS, 0, 0, 0, CONVERSATION_SENDERS_ANYONE);
+        when(mMockINotificationManager.getConsolidatedNotificationPolicy())
+                .thenReturn(policy);
+
+        when(mMockINotificationManager.getBubblePreferenceForPackage(anyString(), anyInt()))
+                .thenReturn(BUBBLE_PREFERENCE_ALL);
+        when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+        mConversationChannel.setImportance(IMPORTANCE_HIGH);
+        mConversationChannel.setImportantConversation(false);
+        mConversationChannel.setAllowBubbles(true);
+        mNotificationInfo.bindNotification(
+                -1,
+                mShortcutManager,
+                mMockPackageManager,
+                mPeopleSpaceWidgetManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                mBubbleMetadata,
+                null,
+                null,
+                mIconFactory,
+                mContext,
+                true,
+                mTestHandler,
+                mTestHandler, null, Optional.of(mBubblesManager),
+                mShadeController);
+        assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
+                .isEqualTo(mContext.getString(
+                        R.string.notification_channel_summary_priority_all));
     }
 
     @Test
@@ -649,7 +738,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -696,7 +784,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -742,7 +829,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -789,7 +875,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -829,7 +914,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -868,7 +952,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -911,7 +994,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -944,7 +1026,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -976,7 +1057,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1015,7 +1095,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1054,7 +1133,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1092,7 +1170,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1129,7 +1206,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1157,7 +1233,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1168,97 +1243,8 @@
     }
 
     @Test
-    public void testSelectPriorityPresentsOnboarding_firstTime() {
-        // GIVEN pref is false
-        Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, false);
-
-        // GIVEN the priority onboarding screen is present
-        PriorityOnboardingDialogController.Builder b =
-                mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
-        PriorityOnboardingDialogController controller =
-                mock(PriorityOnboardingDialogController.class);
-        when(b.build()).thenReturn(controller);
-
-        // GIVEN the user is changing conversation settings
-        mNotificationInfo.bindNotification(
-                -1,
-                mShortcutManager,
-                mMockPackageManager,
-                mPeopleSpaceWidgetManager,
-                mMockINotificationManager,
-                mOnUserInteractionCallback,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mEntry,
-                mBubbleMetadata,
-                null,
-                null,
-                mIconFactory,
-                mContext,
-                () -> b,
-                true,
-                mTestHandler,
-                mTestHandler, null, Optional.of(mBubblesManager),
-                mShadeController);
-
-        // WHEN user clicks "priority"
-        mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
-        verify(controller, never()).show();
-
-        // and then done
-        mNotificationInfo.findViewById(R.id.done).performClick();
-
-        // THEN the user is presented with the priority onboarding screen
-        verify(controller, atLeastOnce()).show();
-    }
-
-    @Test
-    public void testSelectPriorityDoesNotShowOnboarding_secondTime() {
-        //WHEN pref is true
-        Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
-
-        PriorityOnboardingDialogController.Builder b =
-                mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
-        PriorityOnboardingDialogController controller =
-                mock(PriorityOnboardingDialogController.class);
-        when(b.build()).thenReturn(controller);
-
-        mNotificationInfo.bindNotification(
-                -1,
-                mShortcutManager,
-                mMockPackageManager,
-                mPeopleSpaceWidgetManager,
-                mMockINotificationManager,
-                mOnUserInteractionCallback,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                mEntry,
-                mBubbleMetadata,
-                null,
-                null,
-                mIconFactory,
-                mContext,
-                () -> b,
-                true,
-                mTestHandler,
-                mTestHandler, null, Optional.of(mBubblesManager),
-                mShadeController);
-
-        // WHEN user clicks "priority"
-        mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
-        verify(controller, never()).show();
-
-        // and then done
-        mNotificationInfo.findViewById(R.id.done).performClick();
-
-        // THEN the user is presented with the priority onboarding screen
-        verify(controller, never()).show();
-    }
-
-    @Test
     public void testSelectPriorityRequestsPinPeopleTile() {
-        //WHEN pref is true and channel is default importance
-        Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
+        //WHEN channel is default importance
         mNotificationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
                 -1,
@@ -1275,7 +1261,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1293,9 +1278,6 @@
 
     @Test
     public void testSelectDefaultDoesNotRequestPinPeopleTile() {
-        //WHEN pref is true
-        Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
-
         mNotificationInfo.bindNotification(
                 -1,
                 mShortcutManager,
@@ -1311,7 +1293,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
@@ -1329,8 +1310,6 @@
 
     @Test
     public void testSelectPriority_AlreadyPriority_DoesNotRequestPinPeopleTile() {
-        //WHEN pref is true and channel is priority
-        Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING_IN_S, true);
         mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
         mConversationChannel.setImportance(IMPORTANCE_HIGH);
         mConversationChannel.setImportantConversation(true);
@@ -1350,7 +1329,6 @@
                 null,
                 mIconFactory,
                 mContext,
-                mBuilderProvider,
                 true,
                 mTestHandler,
                 mTestHandler, null, Optional.of(mBubblesManager),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index bfce2a5..9f537f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -139,9 +139,6 @@
     @Mock private BubblesManager mBubblesManager;
     @Mock private ShadeController mShadeController;
     @Mock private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
-    @Mock(answer = Answers.RETURNS_SELF)
-    private PriorityOnboardingDialogController.Builder mBuilder;
-    private Provider<PriorityOnboardingDialogController.Builder> mProvider = () -> mBuilder;
     @Mock private AssistantFeedbackController mAssistantFeedbackController;
 
     @Before
@@ -163,7 +160,7 @@
                 () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
                 mINotificationManager, mNotificationEntryManager, mPeopleSpaceWidgetManager,
                 mLauncherApps, mShortcutManager, mChannelEditorDialogController, mContextTracker,
-                mProvider, mAssistantFeedbackController, Optional.of(mBubblesManager),
+                mAssistantFeedbackController, Optional.of(mBubblesManager),
                 new UiEventLoggerFake(), mOnUserInteractionCallback, mShadeController);
         mGutsManager.setUpWithPresenter(mPresenter, mNotificationListContainer,
                 mCheckSaveListener, mOnSettingsClickListener);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 61de53a..7403af7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,6 +16,9 @@
 
 package com.android.server.accessibility;
 
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+
 import android.annotation.MainThread;
 import android.content.Context;
 import android.graphics.Region;
@@ -557,12 +560,16 @@
         MagnificationGestureHandler magnificationGestureHandler;
         if (mAms.getMagnificationMode(displayId)
                 == Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) {
-            magnificationGestureHandler = new WindowMagnificationGestureHandler(displayContext,
+            final Context uiContext = displayContext.createWindowContext(
+                    TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */);
+            magnificationGestureHandler = new WindowMagnificationGestureHandler(uiContext,
                     mAms.getWindowMagnificationMgr(), mAms.getMagnificationController(),
                     detectControlGestures, triggerable,
                     displayId);
         } else {
-            magnificationGestureHandler = new FullScreenMagnificationGestureHandler(displayContext,
+            final Context uiContext = displayContext.createWindowContext(
+                    TYPE_MAGNIFICATION_OVERLAY, null /* options */);
+            magnificationGestureHandler = new FullScreenMagnificationGestureHandler(uiContext,
                     mAms.getFullScreenMagnificationController(), mAms.getMagnificationController(),
                     detectControlGestures, triggerable,
                     new WindowMagnificationPromptController(displayContext, mUserId), displayId);
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 2434e2c..f7d1b9a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -34,6 +34,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiContext;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -139,7 +140,7 @@
     private PointerCoords[] mTempPointerCoords;
     private PointerProperties[] mTempPointerProperties;
 
-    public FullScreenMagnificationGestureHandler(Context context,
+    public FullScreenMagnificationGestureHandler(@UiContext Context context,
             FullScreenMagnificationController fullScreenMagnificationController,
             Callback callback,
             boolean detectTripleTap,
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
index 07f22dc..c5495d9 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/PanningScalingHandler.java
@@ -19,6 +19,7 @@
 import static java.lang.Math.abs;
 
 import android.annotation.NonNull;
+import android.annotation.UiContext;
 import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
@@ -63,8 +64,8 @@
     private boolean mScaling;
     private boolean mEnable;
 
-    PanningScalingHandler(Context context, float maxScale, float minScale, boolean blockScroll,
-            @NonNull MagnificationDelegate magnificationDelegate) {
+    PanningScalingHandler(@UiContext Context context, float maxScale, float minScale,
+            boolean blockScroll, @NonNull MagnificationDelegate magnificationDelegate) {
         mDisplayId = context.getDisplayId();
         mMaxScale = maxScale;
         mMinScale = minScale;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index fa34062..4fb9a03 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -24,6 +24,7 @@
 import static java.util.Arrays.copyOfRange;
 
 import android.annotation.Nullable;
+import android.annotation.UiContext;
 import android.content.Context;
 import android.graphics.Point;
 import android.provider.Settings;
@@ -86,7 +87,7 @@
     private final Context mContext;
     private final Point mTempPoint = new Point();
 
-    public WindowMagnificationGestureHandler(Context context,
+    public WindowMagnificationGestureHandler(@UiContext Context context,
             WindowMagnificationManager windowMagnificationMgr,
             Callback callback,
             boolean detectTripleTap, boolean detectShortcutTrigger, int displayId) {
@@ -342,7 +343,7 @@
          */
         private final boolean mDetectTripleTap;
 
-        DetectingState(Context context, boolean detectTripleTap) {
+        DetectingState(@UiContext Context context, boolean detectTripleTap) {
             mDetectTripleTap = detectTripleTap;
             final MultiTap multiTap = new MultiTap(context, mDetectTripleTap ? 3 : 1,
                     mDetectTripleTap
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/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 6cd7eb7..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.
@@ -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,
@@ -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("=");
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/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/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 08a7d9e..0840e75 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -123,7 +123,7 @@
 
 import android.Manifest;
 import android.Manifest.permission;
-import android.annotation.CallbackExecutor;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -492,7 +492,7 @@
     private DeviceIdleManager mDeviceIdleManager;
     private IUriGrantsManager mUgm;
     private UriGrantsManagerInternal mUgmInternal;
-    private RoleObserver mRoleObserver;
+    private volatile RoleObserver mRoleObserver;
     private UserManager mUm;
     private IPlatformCompat mPlatformCompat;
     private ShortcutHelper mShortcutHelper;
@@ -629,6 +629,8 @@
     static class Archive {
         final SparseArray<Boolean> mEnabled;
         final int mBufferSize;
+        final Object mBufferLock = new Object();
+        @GuardedBy("mBufferLock")
         final LinkedList<Pair<StatusBarNotification, Integer>> mBuffer;
 
         public Archive(int size) {
@@ -651,14 +653,16 @@
             if (!mEnabled.get(sbn.getNormalizedUserId(), false)) {
                 return;
             }
-            if (mBuffer.size() == mBufferSize) {
-                mBuffer.removeFirst();
-            }
+            synchronized (mBufferLock) {
+                if (mBuffer.size() == mBufferSize) {
+                    mBuffer.removeFirst();
+                }
 
-            // We don't want to store the heavy bits of the notification in the archive,
-            // but other clients in the system process might be using the object, so we
-            // store a (lightened) copy.
-            mBuffer.addLast(new Pair<>(sbn.cloneLight(), reason));
+                // We don't want to store the heavy bits of the notification in the archive,
+                // but other clients in the system process might be using the object, so we
+                // store a (lightened) copy.
+                mBuffer.addLast(new Pair<>(sbn.cloneLight(), reason));
+            }
         }
 
         public Iterator<Pair<StatusBarNotification, Integer>> descendingIterator() {
@@ -666,27 +670,31 @@
         }
 
         public StatusBarNotification[] getArray(int count, boolean includeSnoozed) {
-            if (count == 0) count = mBufferSize;
-            List<StatusBarNotification> a = new ArrayList();
-            Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator();
-            int i=0;
-            while (iter.hasNext() && i < count) {
-                Pair<StatusBarNotification, Integer> pair = iter.next();
-                if (pair.second != REASON_SNOOZED || includeSnoozed) {
-                    i++;
-                    a.add(pair.first);
+            synchronized (mBufferLock) {
+                if (count == 0) count = mBufferSize;
+                List<StatusBarNotification> a = new ArrayList();
+                Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator();
+                int i = 0;
+                while (iter.hasNext() && i < count) {
+                    Pair<StatusBarNotification, Integer> pair = iter.next();
+                    if (pair.second != REASON_SNOOZED || includeSnoozed) {
+                        i++;
+                        a.add(pair.first);
+                    }
                 }
+                return a.toArray(new StatusBarNotification[a.size()]);
             }
-            return  a.toArray(new StatusBarNotification[a.size()]);
         }
 
         public void updateHistoryEnabled(@UserIdInt int userId, boolean enabled) {
             mEnabled.put(userId, enabled);
 
             if (!enabled) {
-                for (int i = mBuffer.size() - 1; i >= 0; i--) {
-                    if (userId == mBuffer.get(i).first.getNormalizedUserId()) {
-                        mBuffer.remove(i);
+                synchronized (mBufferLock) {
+                    for (int i = mBuffer.size() - 1; i >= 0; i--) {
+                        if (userId == mBuffer.get(i).first.getNormalizedUserId()) {
+                            mBuffer.remove(i);
+                        }
                     }
                 }
             }
@@ -695,15 +703,18 @@
         // Remove notifications with the specified user & channel ID.
         public void removeChannelNotifications(String pkg, @UserIdInt int userId,
                 String channelId) {
-            Iterator<Pair<StatusBarNotification, Integer>> bufferIter = mBuffer.iterator();
-            while (bufferIter.hasNext()) {
-                final Pair<StatusBarNotification, Integer> pair = bufferIter.next();
-                if (pair.first != null
-                        && userId == pair.first.getNormalizedUserId()
-                        && pkg != null && pkg.equals(pair.first.getPackageName())
-                        && pair.first.getNotification() != null
-                        && Objects.equals(channelId, pair.first.getNotification().getChannelId())) {
-                    bufferIter.remove();
+            synchronized (mBufferLock) {
+                Iterator<Pair<StatusBarNotification, Integer>> bufferIter = descendingIterator();
+                while (bufferIter.hasNext()) {
+                    final Pair<StatusBarNotification, Integer> pair = bufferIter.next();
+                    if (pair.first != null
+                            && userId == pair.first.getNormalizedUserId()
+                            && pkg != null && pkg.equals(pair.first.getPackageName())
+                            && pair.first.getNotification() != null
+                            && Objects.equals(channelId,
+                            pair.first.getNotification().getChannelId())) {
+                        bufferIter.remove();
+                    }
                 }
             }
         }
@@ -2640,6 +2651,11 @@
 
     @Override
     public void onBootPhase(int phase) {
+        onBootPhase(phase, Looper.getMainLooper());
+    }
+
+    @VisibleForTesting
+    void onBootPhase(int phase, Looper mainLooper) {
         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
             // no beeping until we're basically done booting
             mSystemReady = true;
@@ -2649,9 +2665,11 @@
             mAudioManagerInternal = getLocalService(AudioManagerInternal.class);
             mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
             mZenModeHelper.onSystemReady();
-            mRoleObserver = new RoleObserver(getContext().getSystemService(RoleManager.class),
-                    mPackageManager, getContext().getMainExecutor());
-            mRoleObserver.init();
+            RoleObserver roleObserver = new RoleObserver(getContext(),
+                    getContext().getSystemService(RoleManager.class),
+                    mPackageManager, mainLooper);
+            roleObserver.init();
+            mRoleObserver = roleObserver;
             LauncherApps launcherApps =
                     (LauncherApps) getContext().getSystemService(Context.LAUNCHER_APPS_SERVICE);
             mShortcutHelper = new ShortcutHelper(launcherApps, mShortcutListener, getLocalService(
@@ -10677,26 +10695,40 @@
         // Role name : user id : list of approved packages
         private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;
 
+        /**
+         * Writes should be pretty rare (only when default browser changes) and reads are done
+         * during activity start code-path, so we're optimizing for reads. This means this set is
+         * immutable once written and we'll recreate the set every time there is a role change and
+         * then assign that new set to the volatile below, so reads can be done without needing to
+         * hold a lock. Every write is done on the main-thread, so write atomicity is guaranteed.
+         *
+         * Didn't use unmodifiable set to enforce immutability to avoid iterating via iterators.
+         */
+        private volatile ArraySet<Integer> mTrampolineExemptUids = new ArraySet<>();
+
         private final RoleManager mRm;
         private final IPackageManager mPm;
         private final Executor mExecutor;
+        private final Looper mMainLooper;
 
-        RoleObserver(@NonNull RoleManager roleManager,
-                @NonNull IPackageManager pkgMgr,
-                @NonNull @CallbackExecutor Executor executor) {
+        RoleObserver(Context context, @NonNull RoleManager roleManager,
+                @NonNull IPackageManager pkgMgr, @NonNull Looper mainLooper) {
             mRm = roleManager;
             mPm = pkgMgr;
-            mExecutor = executor;
+            mExecutor = context.getMainExecutor();
+            mMainLooper = mainLooper;
         }
 
+        /** Should be called from the main-thread. */
+        @MainThread
         public void init() {
-            List<UserInfo> users = mUm.getUsers();
+            List<UserHandle> users = mUm.getUserHandles(/* excludeDying */ true);
             mNonBlockableDefaultApps = new ArrayMap<>();
             for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
                 final ArrayMap<Integer, ArraySet<String>> userToApprovedList = new ArrayMap<>();
                 mNonBlockableDefaultApps.put(NON_BLOCKABLE_DEFAULT_ROLES[i], userToApprovedList);
                 for (int j = 0; j < users.size(); j++) {
-                    Integer userId = users.get(j).getUserHandle().getIdentifier();
+                    Integer userId = users.get(j).getIdentifier();
                     ArraySet<String> approvedForUserId = new ArraySet<>(mRm.getRoleHoldersAsUser(
                             NON_BLOCKABLE_DEFAULT_ROLES[i], UserHandle.of(userId)));
                     ArraySet<Pair<String, Integer>> approvedAppUids = new ArraySet<>();
@@ -10707,7 +10739,7 @@
                     mPreferencesHelper.updateDefaultApps(userId, null, approvedAppUids);
                 }
             }
-
+            updateTrampolineExemptUidsForUsers(users.toArray(new UserHandle[0]));
             mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
         }
 
@@ -10716,6 +10748,11 @@
             return mNonBlockableDefaultApps.get(role).get(userId).contains(pkg);
         }
 
+        @VisibleForTesting
+        public boolean isUidExemptFromTrampolineRestrictions(int uid) {
+            return mTrampolineExemptUids.contains(uid);
+        }
+
         /**
          * Convert the assistant-role holder into settings. The rest of the system uses the
          * settings.
@@ -10725,6 +10762,12 @@
          */
         @Override
         public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+            onRoleHoldersChangedForNonBlockableDefaultApps(roleName, user);
+            onRoleHoldersChangedForTrampolines(roleName, user);
+        }
+
+        private void onRoleHoldersChangedForNonBlockableDefaultApps(@NonNull String roleName,
+                @NonNull UserHandle user) {
             // we only care about a couple of the roles they'll tell us about
             boolean relevantChange = false;
             for (int i = 0; i < NON_BLOCKABLE_DEFAULT_ROLES.length; i++) {
@@ -10772,6 +10815,41 @@
             // write of the notification policy xml for this change
         }
 
+        private void onRoleHoldersChangedForTrampolines(@NonNull String roleName,
+                @NonNull UserHandle user) {
+            if (!RoleManager.ROLE_BROWSER.equals(roleName)) {
+                return;
+            }
+            updateTrampolineExemptUidsForUsers(user);
+        }
+
+        private void updateTrampolineExemptUidsForUsers(UserHandle... users) {
+            Preconditions.checkState(mMainLooper.isCurrentThread());
+            ArraySet<Integer> oldUids = mTrampolineExemptUids;
+            ArraySet<Integer> newUids = new ArraySet<>();
+            // Add the uids from previous set for the users that we won't update.
+            for (int i = 0, n = oldUids.size(); i < n; i++) {
+                int uid = oldUids.valueAt(i);
+                UserHandle user = UserHandle.of(UserHandle.getUserId(uid));
+                if (!ArrayUtils.contains(users, user)) {
+                    newUids.add(uid);
+                }
+            }
+            // Now lookup the new uids for the users that we want to update.
+            for (int i = 0, n = users.length; i < n; i++) {
+                UserHandle user = users[i];
+                for (String pkg : mRm.getRoleHoldersAsUser(RoleManager.ROLE_BROWSER, user)) {
+                    int uid = getUidForPackage(pkg, user.getIdentifier());
+                    if (uid != -1) {
+                        newUids.add(uid);
+                    } else {
+                        Slog.e(TAG, "Bad uid (-1) for browser package " + pkg);
+                    }
+                }
+            }
+            mTrampolineExemptUids = newUids;
+        }
+
         private int getUidForPackage(String pkg, int userId) {
             try {
                 return mPm.getPackageUid(pkg, MATCH_ALL, userId);
@@ -10936,7 +11014,7 @@
             }
             String logcatMessage =
                     "Indirect notification activity start (trampoline) from " + packageName;
-            if (CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid)) {
+            if (blockTrampoline(uid)) {
                 // Post toast() call to mHandler to offload PM lookup from the activity start path
                 mHandler.post(() -> toast(packageName, uid));
                 Slog.e(TAG, logcatMessage + " blocked");
@@ -10947,6 +11025,13 @@
             }
         }
 
+        private boolean blockTrampoline(int uid) {
+            if (mRoleObserver != null && mRoleObserver.isUidExemptFromTrampolineRestrictions(uid)) {
+                return false;
+            }
+            return CompatChanges.isChangeEnabled(NOTIFICATION_TRAMPOLINE_BLOCK, uid);
+        }
+
         @Override
         public boolean canCloseSystemDialogs(Collection<IBinder> tokens, int uid) {
             // If the start is allowed via notification, we allow the app to close system dialogs
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 dad37f4..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
@@ -23604,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/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 3dfb835e..1bd9e5e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -319,7 +319,7 @@
             }
             final Icon icon = si.getIcon();
             if (icon != null && icon.getType() != Icon.TYPE_BITMAP
-                    && icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) {
+                    && icon.getType() != Icon.TYPE_ADAPTIVE_BITMAP) {
                 continue;
             }
             if (icon == null && !si.hasIconFile()) {
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/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..8f3702a 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;
@@ -6618,9 +6619,15 @@
 
     @Override
     void onCancelFixedRotationTransform(int originalDisplayRotation) {
-        if (this != mDisplayContent.getLastOrientationSource()
-                || getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED) {
-            // Only need to handle the activity that should be rotated with display.
+        if (this != mDisplayContent.getLastOrientationSource()) {
+            // This activity doesn't affect display rotation.
+            return;
+        }
+        final int requestedOrientation = getRequestedConfigurationOrientation();
+        if (requestedOrientation != ORIENTATION_UNDEFINED
+                && requestedOrientation != mDisplayContent.getConfiguration().orientation) {
+            // Only need to handle the activity that can be rotated with display or the activity
+            // has requested the same orientation.
             return;
         }
 
@@ -6858,6 +6865,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 9aabdac..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) {
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 074eeb9..9de5058 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -7244,6 +7244,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        checkAllUsersAreAffiliatedWithDevice();
         mInjector.binderWithCleanCallingIdentity(
                 () -> mInjector.getConnectivityManager().setGlobalProxy(proxyInfo));
     }
@@ -15736,6 +15737,7 @@
         Objects.requireNonNull(who, "ComponentName is null");
         final CallerIdentity caller = getCallerIdentity(who);
         Preconditions.checkCallAuthorization(isDeviceOwner(caller));
+        checkAllUsersAreAffiliatedWithDevice();
         checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_GLOBAL_PRIVATE_DNS);
 
         switch (mode) {
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/net/Android.bp b/services/net/Android.bp
index 800f7ad..f92db86 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -52,7 +52,8 @@
     libs: [
         "unsupportedappusage",
         "framework-wifi-util-lib",
-        "framework-connectivity"
+        "framework-connectivity",
+        "modules-utils-build_system",
     ],
     static_libs: [
         // All the classes in netd_aidl_interface must be jarjar so they do not conflict with the
@@ -60,6 +61,7 @@
         "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-client",
+        "modules-utils-build_system",
     ],
     apex_available: [
         "com.android.wifi",
@@ -80,7 +82,7 @@
     ],
     visibility: [
         "//frameworks/base/packages/Tethering",
-        "//packages/modules/Connectivity/Tethering"
+        "//packages/modules/Connectivity/Tethering",
     ],
 }
 
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/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 1b42dfa..c54dffc 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -30,6 +30,7 @@
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
 import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_SET_NO_ERROR;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
@@ -7333,6 +7334,48 @@
         assertThat(dpm.getPolicyExemptApps()).containsExactly("4", "8", "15", "16", "23", "42");
     }
 
+    @Test
+    public void testSetGlobalPrivateDnsModeOpportunistic_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+        // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+        // feature is disabled because there are non-affiliated secondary users.
+        getServices().removeUser(CALLER_USER_HANDLE);
+        clearInvocations(getServices().settings);
+
+        int result = dpm.setGlobalPrivateDnsModeOpportunistic(admin1);
+
+        assertThat(result).isEqualTo(PRIVATE_DNS_SET_NO_ERROR);
+    }
+
+    @Test
+    public void testSetGlobalPrivateDnsModeOpportunistic_hasUnaffiliatedUsers() throws Exception {
+        setDeviceOwner();
+        setAsProfileOwner(admin2);
+
+        assertThrows(SecurityException.class,
+                () -> dpm.setGlobalPrivateDnsModeOpportunistic(admin1));
+    }
+
+    @Test
+    public void testSetRecommendedGlobalProxy_asDeviceOwner() throws Exception {
+        setDeviceOwner();
+        // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
+        // feature is disabled because there are non-affiliated secondary users.
+        getServices().removeUser(CALLER_USER_HANDLE);
+
+        dpm.setRecommendedGlobalProxy(admin1, null);
+
+        verify(getServices().connectivityManager).setGlobalProxy(null);
+    }
+
+    @Test
+    public void testSetRecommendedGlobalProxy_hasUnaffiliatedUsers() throws Exception {
+        setDeviceOwner();
+        setAsProfileOwner(admin2);
+
+        assertThrows(SecurityException.class, () -> dpm.setRecommendedGlobalProxy(admin1, null));
+    }
+
     private void setUserUnlocked(int userHandle, boolean unlocked) {
         when(getServices().userManager.isUserUnlocked(eq(userHandle))).thenReturn(unlocked);
     }
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/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
index a05fea2..1126e1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
@@ -21,6 +21,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
+
 import android.app.Notification;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -37,7 +39,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.ConcurrentModificationException;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -165,4 +171,54 @@
             assertThat(expected).contains(sbn.getKey());
         }
     }
+
+    @Test
+    public void testRemoveChannelNotifications_concurrently() throws InterruptedException {
+        List<String> expected = new ArrayList<>();
+        // Add one extra notification to the beginning to test when 2 adjacent notifications will be
+        // removed in the same pass.
+        StatusBarNotification sbn0 = getNotification("pkg", 0, UserHandle.of(USER_CURRENT));
+        mArchive.record(sbn0, REASON_CANCEL);
+        for (int i = 0; i < SIZE; i++) {
+            StatusBarNotification sbn = getNotification("pkg", i, UserHandle.of(USER_CURRENT));
+            mArchive.record(sbn, REASON_CANCEL);
+            if (i >= SIZE - 2) {
+                // Remove everything < SIZE - 2
+                expected.add(sbn.getKey());
+            }
+        }
+
+        // Remove these in multiple threads to try to get them to happen at the same time
+        int numThreads = SIZE - 2;
+        AtomicBoolean error = new AtomicBoolean(false);
+        CountDownLatch startThreadsLatch = new CountDownLatch(1);
+        CountDownLatch threadsDone = new CountDownLatch(numThreads);
+        for (int i = 0; i < numThreads; i++) {
+            final int idx = i;
+            new Thread(() -> {
+                try {
+                    startThreadsLatch.await(10, TimeUnit.SECONDS);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                try {
+                    mArchive.removeChannelNotifications("pkg", USER_CURRENT, "test" + idx);
+                } catch (ConcurrentModificationException e) {
+                    error.compareAndSet(false, true);
+                }
+            }).start();
+        }
+
+        startThreadsLatch.countDown();
+        threadsDone.await(10, TimeUnit.SECONDS);
+        if (error.get()) {
+            fail("Concurrent modification exception");
+        }
+
+        List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(SIZE, true));
+        assertThat(actual).hasSize(expected.size());
+        for (StatusBarNotification sbn : actual) {
+            assertThat(expected).contains(sbn.getKey());
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 537bc2c..c33287c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -141,6 +141,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Process;
 import android.os.RemoteException;
@@ -267,6 +268,8 @@
     RankingHandler mRankingHandler;
     @Mock
     ActivityManagerInternal mAmi;
+    @Mock
+    private Looper mMainLooper;
 
     @Mock
     IIntentSender pi1;
@@ -514,7 +517,9 @@
                 mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal,
                 mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class),
                 mAmi, mToastRateLimiter);
-        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+        // Return first true for RoleObserver main-thread check
+        when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
+        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
 
         mService.setAudioManager(mAudioManager);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 4ce237e..27ae46c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.app.role.RoleManager.ROLE_BROWSER;
 import static android.app.role.RoleManager.ROLE_DIALER;
 import static android.app.role.RoleManager.ROLE_EMERGENCY;
 import static android.content.pm.PackageManager.MATCH_ALL;
@@ -23,6 +24,7 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
@@ -31,6 +33,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static java.util.Arrays.asList;
+
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
@@ -44,7 +48,6 @@
 import android.content.Context;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -80,7 +83,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -98,13 +100,13 @@
     @Mock
     private UserManager mUm;
     @Mock
-    private Executor mExecutor;
-    @Mock
     private RoleManager mRoleManager;
+    @Mock
+    private Looper mMainLooper;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
-    private List<UserInfo> mUsers;
+    private List<UserHandle> mUsers;
 
     private static class TestableNotificationManagerService extends NotificationManagerService {
         TestableNotificationManagerService(Context context,
@@ -133,13 +135,15 @@
         mContext.addMockSystemService(AppOpsManager.class, mock(AppOpsManager.class));
 
         mUsers = new ArrayList<>();
-        mUsers.add(new UserInfo(0, "system", 0));
-        mUsers.add(new UserInfo(10, "second", 0));
-        when(mUm.getUsers()).thenReturn(mUsers);
+        mUsers.add(new UserHandle(0));
+        mUsers.add(new UserHandle(10));
+        when(mUm.getUserHandles(anyBoolean())).thenReturn(mUsers);
+
+        when(mMainLooper.isCurrentThread()).thenReturn(true);
 
         mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
                 mNotificationInstanceIdSequence);
-        mRoleObserver = mService.new RoleObserver(mRoleManager, mPm, mExecutor);
+        mRoleObserver = mService.new RoleObserver(mContext, mRoleManager, mPm, mMainLooper);
 
         try {
             mService.init(mService.new WorkerHandler(mock(Looper.class)),
@@ -174,7 +178,7 @@
     }
 
     @Test
-    public void testInit() throws Exception {
+    public void testInit_forNonBlockableDefaultApps() throws Exception {
         List<String> dialer0 = new ArrayList<>();
         dialer0.add("dialer");
         List<String> emer0 = new ArrayList<>();
@@ -191,29 +195,29 @@
 
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_DIALER,
-                mUsers.get(0).getUserHandle())).
-                thenReturn(dialer0);
+                mUsers.get(0)))
+                .thenReturn(dialer0);
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_EMERGENCY,
-                mUsers.get(0).getUserHandle())).
-                thenReturn(emer0);
+                mUsers.get(0)))
+                .thenReturn(emer0);
 
         mRoleObserver.init();
 
         // verify internal records of current state of the world
         assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
-                ROLE_DIALER, dialer0.get(0), mUsers.get(0).id));
+                ROLE_DIALER, dialer0.get(0), mUsers.get(0).getIdentifier()));
         assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
-                ROLE_DIALER, dialer0.get(0), mUsers.get(1).id));
+                ROLE_DIALER, dialer0.get(0), mUsers.get(1).getIdentifier()));
 
         assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(
-                ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).id));
+                ROLE_EMERGENCY, emer0.get(0), mUsers.get(0).getIdentifier()));
         assertFalse(mRoleObserver.isApprovedPackageForRoleForUser(
-                ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).id));
+                ROLE_EMERGENCY, emer0.get(0), mUsers.get(1).getIdentifier()));
 
         // make sure we're listening to updates
         verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(
-                eq(mExecutor), any(), eq(UserHandle.ALL));
+                any(), any(), eq(UserHandle.ALL));
 
         // make sure we told pref helper about the state of the world
         verify(mPreferencesHelper, times(1)).updateDefaultApps(0, null, dialer0Pair);
@@ -221,14 +225,31 @@
     }
 
     @Test
-    public void testSwapDefault() throws Exception {
+    public void testInit_forTrampolines() throws Exception {
+        when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_BROWSER,
+                mUsers.get(0)))
+                .thenReturn(asList("com.browser"));
+
+        mRoleObserver.init();
+
+        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+
+        // make sure we're listening to updates
+        verify(mRoleManager, times(1)).addOnRoleHoldersChangedListenerAsUser(any(), any(),
+                eq(UserHandle.ALL));
+    }
+
+    @Test
+    public void testSwapDefault_forNonBlockableDefaultApps() throws Exception {
         List<String> dialer0 = new ArrayList<>();
         dialer0.add("dialer");
 
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_DIALER,
-                mUsers.get(0).getUserHandle())).
-                thenReturn(dialer0);
+                mUsers.get(0)))
+                .thenReturn(dialer0);
 
         mRoleObserver.init();
 
@@ -241,8 +262,8 @@
 
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_DIALER,
-                mUsers.get(0).getUserHandle())).
-                thenReturn(newDefault);
+                mUsers.get(0)))
+                .thenReturn(newDefault);
 
         mRoleObserver.onRoleHoldersChanged(ROLE_DIALER, UserHandle.of(0));
 
@@ -251,15 +272,39 @@
     }
 
     @Test
-    public void testSwapDefault_multipleOverlappingApps() throws Exception {
+    public void testSwapDefault_forTrampolines() throws Exception {
+        List<String> dialer0 = new ArrayList<>();
+        when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
+        when(mPm.getPackageUid("com.browser2", MATCH_ALL, 0)).thenReturn(31);
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_BROWSER,
+                mUsers.get(0)))
+                .thenReturn(asList("com.browser"));
+        mRoleObserver.init();
+        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+        assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(31));
+        // Default changed
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_BROWSER,
+                mUsers.get(0)))
+                .thenReturn(asList("com.browser2"));
+        mRoleObserver.onRoleHoldersChanged(ROLE_BROWSER, UserHandle.of(0));
+
+        assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(31));
+    }
+
+    @Test
+    public void testSwapDefault_multipleOverlappingApps_forNonBlockableDefaultApps()
+            throws Exception {
         List<String> dialer0 = new ArrayList<>();
         dialer0.add("dialer");
         dialer0.add("phone");
 
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_DIALER,
-                mUsers.get(0).getUserHandle())).
-                thenReturn(dialer0);
+                mUsers.get(0)))
+                .thenReturn(dialer0);
 
         mRoleObserver.init();
 
@@ -273,8 +318,8 @@
 
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_DIALER,
-                mUsers.get(0).getUserHandle())).
-                thenReturn(newDefault);
+                mUsers.get(0)))
+                .thenReturn(newDefault);
 
         ArraySet<String> expectedRemove = new ArraySet<>();
         expectedRemove.add("dialer");
@@ -294,14 +339,14 @@
     }
 
     @Test
-    public void testSwapDefault_newUser() throws Exception {
+    public void testSwapDefault_newUser_forNonBlockableDefaultApps() throws Exception {
         List<String> dialer0 = new ArrayList<>();
         dialer0.add("dialer");
 
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_DIALER,
-                mUsers.get(0).getUserHandle())).
-                thenReturn(dialer0);
+                mUsers.get(0)))
+                .thenReturn(dialer0);
 
         mRoleObserver.init();
 
@@ -310,8 +355,8 @@
 
         when(mRoleManager.getRoleHoldersAsUser(
                 ROLE_DIALER,
-                mUsers.get(1).getUserHandle())).
-                thenReturn(dialer10);
+                mUsers.get(1)))
+                .thenReturn(dialer10);
 
         ArraySet<Pair<String, Integer>> expectedAddPair = new ArraySet<>();
         expectedAddPair.add(new Pair("phone", 30));
@@ -329,4 +374,27 @@
         assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "phone", 10));
         assertTrue(mRoleObserver.isApprovedPackageForRoleForUser(ROLE_DIALER, "dialer", 0));
     }
+
+    @Test
+    public void testSwapDefault_newUser_forTrampolines() throws Exception {
+        List<String> dialer0 = new ArrayList<>();
+        when(mPm.getPackageUid("com.browser", MATCH_ALL, 0)).thenReturn(30);
+        when(mPm.getPackageUid("com.browser2", MATCH_ALL, 10)).thenReturn(1031);
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_BROWSER,
+                mUsers.get(0)))
+                .thenReturn(asList("com.browser"));
+        mRoleObserver.init();
+        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+        assertFalse(mRoleObserver.isUidExemptFromTrampolineRestrictions(1031));
+        // New user
+        when(mRoleManager.getRoleHoldersAsUser(
+                ROLE_BROWSER,
+                mUsers.get(1)))
+                .thenReturn(asList("com.browser2"));
+        mRoleObserver.onRoleHoldersChanged(ROLE_BROWSER, UserHandle.of(10));
+
+        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(30));
+        assertTrue(mRoleObserver.isUidExemptFromTrampolineRestrictions(1031));
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index fdeb7a6..4bbea94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1723,6 +1723,15 @@
 
         assertFalse(display.hasTopFixedRotationLaunchingApp());
         assertFalse(activity.hasFixedRotationTransform());
+
+        // Simulate that the activity requests the same orientation as display.
+        activity.setOrientation(display.getConfiguration().orientation);
+        // Skip the real freezing.
+        activity.mVisibleRequested = false;
+        clearInvocations(activity);
+        activity.onCancelFixedRotationTransform(originalRotation);
+        // The implementation of cancellation must be executed.
+        verify(activity).startFreezingScreen(originalRotation);
     }
 
     @Test
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/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d9001bd..78da86c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15016,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/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
index c01d32b..6ef1ecd 100644
--- a/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
+++ b/tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
@@ -35,7 +35,7 @@
     fun setUp() {
         mViewFrameInfo.reset()
         mViewFrameInfo.setInputEvent(139)
-        mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
+        mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED
         mTimeStarted = SystemClock.uptimeNanos()
         mViewFrameInfo.markDrawStart()
     }
@@ -43,7 +43,7 @@
     @Test
     fun testPopulateFields() {
         assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted)
-        assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
+        assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED)
     }
 
     @Test
@@ -66,7 +66,7 @@
         mViewFrameInfo.populateFrameInfo(frameInfo)
         assertThat(frameInfo.frameInfo[FrameInfo.INPUT_EVENT_ID]).isEqualTo(139)
         assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(
-                FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
+                FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED)
         assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted)
     }
 }
\ No newline at end of file
diff --git a/tests/UpdatableSystemFontTest/Android.bp b/tests/UpdatableSystemFontTest/Android.bp
index 8b0ae5c..ea5a431 100644
--- a/tests/UpdatableSystemFontTest/Android.bp
+++ b/tests/UpdatableSystemFontTest/Android.bp
@@ -21,16 +21,15 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
-java_test_host {
+android_test {
     name: "UpdatableSystemFontTest",
     srcs: ["src/**/*.java"],
-    libs: [
-        "tradefed",
-        "compatibility-tradefed",
-        "compatibility-host-util",
-    ],
+    libs: ["android.test.runner"],
     static_libs: [
-        "frameworks-base-hostutils",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+        "truth-prebuilt",
     ],
     test_suites: [
         "general-tests",
@@ -47,4 +46,5 @@
         ":UpdatableSystemFontTestNotoColorEmojiVPlus2Ttf",
         ":UpdatableSystemFontTestNotoColorEmojiVPlus2TtfFsvSig",
     ],
+    sdk_version: "test_current",
 }
diff --git a/tests/UpdatableSystemFontTest/AndroidManifest.xml b/tests/UpdatableSystemFontTest/AndroidManifest.xml
new file mode 100644
index 0000000..531ee98
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.updatablesystemfont">
+
+    <application android:label="UpdatableSystemFontTest">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="UpdatableSystemFontTest"
+         android:targetPackage="com.android.updatablesystemfont">
+    </instrumentation>
+
+</manifest>
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index 4f11669..4f6487e 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -21,6 +21,7 @@
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="UpdatableSystemFontTest.apk" />
         <option name="test-file-name" value="EmojiRenderingTestApp.apk" />
     </target_preparer>
 
@@ -37,7 +38,7 @@
         <option name="push" value="UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig->/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig" />
     </target_preparer>
 
-    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="UpdatableSystemFontTest.jar" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.updatablesystemfont" />
     </test>
 </configuration>
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index 74f6bca..79e23b8 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -17,26 +17,35 @@
 package com.android.updatablesystemfont;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import android.app.UiAutomation;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.RootPermissionTest;
+import android.security.FileIntegrityManager;
+import android.util.Log;
+import android.util.Pair;
 
-import com.android.fsverity.AddFsVerityCertRule;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.StreamUtil;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -44,9 +53,10 @@
  * Tests if fonts can be updated by 'cmd font'.
  */
 @RootPermissionTest
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
+@RunWith(AndroidJUnit4.class)
+public class UpdatableSystemFontTest {
 
+    private static final String TAG = "UpdatableSystemFontTest";
     private static final String SYSTEM_FONTS_DIR = "/system/fonts/";
     private static final String DATA_FONTS_DIR = "/data/fonts/files/";
 
@@ -84,58 +94,65 @@
         T get() throws Exception;
     }
 
-    @Rule
-    public final AddFsVerityCertRule mAddFsverityCertRule =
-            new AddFsVerityCertRule(this, CERT_PATH);
+    private String mKeyId;
 
     @Before
     public void setUp() throws Exception {
-        expectRemoteCommandToSucceed("cmd font clear");
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        // Run tests only if updatable system font is enabled.
+        FileIntegrityManager fim = context.getSystemService(FileIntegrityManager.class);
+        assumeTrue(fim != null);
+        assumeTrue(fim.isApkVeritySupported());
+        mKeyId = insertCert(CERT_PATH);
+        expectCommandToSucceed("cmd font clear");
     }
 
     @After
     public void tearDown() throws Exception {
-        expectRemoteCommandToSucceed("cmd font clear");
+        expectCommandToSucceed("cmd font clear");
+        if (mKeyId != null) {
+            expectCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
+        }
     }
 
     @Test
     public void updateFont() throws Exception {
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
         assertThat(fontPath).startsWith(DATA_FONTS_DIR);
         // The updated font should be readable and unmodifiable.
-        expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
-        expectRemoteCommandToFail("echo -n '' >> " + fontPath);
+        expectCommandToSucceed("dd status=none if=" + fontPath + " of=/dev/null");
+        expectCommandToFail("dd status=none if=" + CERT_PATH + " of=" + fontPath);
     }
 
     @Test
     public void updateFont_twice() throws Exception {
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
         String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
         assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
         assertThat(fontPath2).isNotEqualTo(fontPath);
         // The new file should be readable.
-        expectRemoteCommandToSucceed("cat " + fontPath2 + " > /dev/null");
+        expectCommandToSucceed("dd status=none if=" + fontPath2 + " of=/dev/null");
         // The old file should be still readable.
-        expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
+        expectCommandToSucceed("dd status=none if=" + fontPath + " of=/dev/null");
     }
 
     @Test
     public void updateFont_allowSameVersion() throws Exception {
         // Update original font to the same version
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 ORIGINAL_NOTO_COLOR_EMOJI_TTF, ORIGINAL_NOTO_COLOR_EMOJI_TTF_FSV_SIG));
         String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
         // Update updated font to the same version
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_TTF);
         assertThat(fontPath).startsWith(DATA_FONTS_DIR);
@@ -147,21 +164,21 @@
 
     @Test
     public void updateFont_invalidCert() throws Exception {
-        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+        expectCommandToFail(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
     }
 
     @Test
     public void updateFont_downgradeFromSystem() throws Exception {
-        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+        expectCommandToFail(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_V0_TTF, TEST_NOTO_COLOR_EMOJI_V0_TTF_FSV_SIG));
     }
 
     @Test
     public void updateFont_downgradeFromData() throws Exception {
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
-        expectRemoteCommandToFail(String.format("cmd font update %s %s",
+        expectCommandToFail(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
     }
 
@@ -178,7 +195,7 @@
     public void launchApp_afterUpdateFont() throws Exception {
         String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
         assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR);
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
         assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR);
@@ -191,57 +208,99 @@
 
     @Test
     public void reboot() throws Exception {
-        expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+        expectCommandToSucceed(String.format("cmd font update %s %s",
                 TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
         String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
         assertThat(fontPath).startsWith(DATA_FONTS_DIR);
 
         // Emulate reboot by 'cmd font restart'.
-        expectRemoteCommandToSucceed("cmd font restart");
+        expectCommandToSucceed("cmd font restart");
         String fontPathAfterReboot = getFontPath(NOTO_COLOR_EMOJI_TTF);
         assertThat(fontPathAfterReboot).isEqualTo(fontPath);
     }
 
-    private String getFontPath(String fontFileName) throws Exception {
+    private static String insertCert(String certPath) throws Exception {
+        Pair<String, String> result;
+        try (InputStream is = new FileInputStream(certPath)) {
+            result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
+        }
+        // Assert that there are no errors.
+        assertThat(result.second).isEmpty();
+        String keyId = result.first.trim();
+        assertThat(keyId).matches("^\\d+$");
+        return keyId;
+    }
+
+    private static String getFontPath(String fontFileName) throws Exception {
         // TODO: add a dedicated command for testing.
-        String lines = expectRemoteCommandToSucceed("cmd font dump");
+        String lines = expectCommandToSucceed("cmd font dump");
         for (String line : lines.split("\n")) {
             Matcher m = PATTERN_FONT.matcher(line);
             if (m.find() && m.group(1).endsWith(fontFileName)) {
                 return m.group(1);
             }
         }
-        CLog.e("Font not found: " + fontFileName);
-        return null;
+        throw new AssertionError("Font not found: " + fontFileName);
     }
 
-    private void startActivity(String appId, String activityId) throws Exception {
+    private static void startActivity(String appId, String activityId) throws Exception {
         // Make sure that the app is installed and enabled.
         waitUntil(ACTIVITY_TIMEOUT_MILLIS, () -> {
-            String packageInfo = expectRemoteCommandToSucceed(
-                    "pm list packages -e " + EMOJI_RENDERING_TEST_APP_ID);
+            String packageInfo = expectCommandToSucceed("pm list packages -e " + appId);
             return !packageInfo.isEmpty();
         });
-        expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
-        expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
+        expectCommandToSucceed("am force-stop " + appId);
+        expectCommandToSucceed("am start-activity -n " + activityId);
     }
 
-    private String expectRemoteCommandToSucceed(String cmd) throws Exception {
-        CommandResult result = getDevice().executeShellV2Command(cmd);
-        assertWithMessage("`" + cmd + "` failed: " + result.getStderr())
-                .that(result.getStatus())
-                .isEqualTo(CommandStatus.SUCCESS);
-        return result.getStdout();
+    private static String expectCommandToSucceed(String cmd) throws IOException {
+        Pair<String, String> result = runShellCommand(cmd, null);
+        // UiAutomation.runShellCommand() does not return exit code.
+        // Assume that the command fails if stderr is not empty.
+        assertThat(result.second.trim()).isEmpty();
+        return result.first;
     }
 
-    private void expectRemoteCommandToFail(String cmd) throws Exception {
-        CommandResult result = getDevice().executeShellV2Command(cmd);
-        assertWithMessage("Unexpected success from `" + cmd + "`: " + result.getStderr())
-                .that(result.getStatus())
-                .isNotEqualTo(CommandStatus.SUCCESS);
+    private static void expectCommandToFail(String cmd) throws IOException {
+        Pair<String, String> result = runShellCommand(cmd, null);
+        // UiAutomation.runShellCommand() does not return exit code.
+        // Assume that the command fails if stderr is not empty.
+        assertThat(result.second.trim()).isNotEmpty();
     }
 
-    private void waitUntil(long timeoutMillis, ThrowingSupplier<Boolean> func) {
+    /** Runs a command and returns (stdout, stderr). */
+    private static Pair<String, String> runShellCommand(String cmd, @Nullable InputStream input)
+            throws IOException  {
+        Log.i(TAG, "runShellCommand: " + cmd);
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        ParcelFileDescriptor[] rwe = automation.executeShellCommandRwe(cmd);
+        // executeShellCommandRwe returns [stdout, stdin, stderr].
+        try (ParcelFileDescriptor outFd = rwe[0];
+             ParcelFileDescriptor inFd = rwe[1];
+             ParcelFileDescriptor errFd = rwe[2]) {
+            if (input != null) {
+                try (OutputStream os = new FileOutputStream(inFd.getFileDescriptor())) {
+                    StreamUtil.copyStreams(input, os);
+                }
+            }
+            // We have to close stdin before reading stdout and stderr.
+            // It's safe to close ParcelFileDescriptor multiple times.
+            inFd.close();
+            String stdout;
+            try (InputStream is = new FileInputStream(outFd.getFileDescriptor())) {
+                stdout = StreamUtil.readInputStream(is);
+            }
+            Log.i(TAG, "stdout =  " + stdout);
+            String stderr;
+            try (InputStream is = new FileInputStream(errFd.getFileDescriptor())) {
+                stderr = StreamUtil.readInputStream(is);
+            }
+            Log.i(TAG, "stderr =  " + stderr);
+            return new Pair<>(stdout, stderr);
+        }
+    }
+
+    private static void waitUntil(long timeoutMillis, ThrowingSupplier<Boolean> func) {
         long untilMillis = System.currentTimeMillis() + timeoutMillis;
         do {
             try {
@@ -256,25 +315,16 @@
         throw new AssertionError("Timed out");
     }
 
-    private boolean isFileOpenedBy(String path, String appId) throws DeviceNotAvailableException {
+    private static boolean isFileOpenedBy(String path, String appId) throws Exception {
         String pid = pidOf(appId);
         if (pid.isEmpty()) {
             return false;
         }
-        CommandResult result = getDevice().executeShellV2Command(
-                String.format("lsof -t -p %s '%s'", pid, path));
-        if (result.getStatus() != CommandStatus.SUCCESS) {
-            return false;
-        }
-        // The file is open if the output of lsof is non-empty.
-        return !result.getStdout().trim().isEmpty();
+        String cmd = String.format("lsof -t -p %s %s", pid, path);
+        return !expectCommandToSucceed(cmd).trim().isEmpty();
     }
 
-    private String pidOf(String appId) throws DeviceNotAvailableException {
-        CommandResult result = getDevice().executeShellV2Command("pidof " + appId);
-        if (result.getStatus() != CommandStatus.SUCCESS) {
-            return "";
-        }
-        return result.getStdout().trim();
+    private static String pidOf(String appId) throws Exception {
+        return expectCommandToSucceed("pidof " + appId).trim();
     }
 }
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(