Merge "Enable compat checking & lint for system_services api"
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index bf6afe7..a4e8fdc 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -8231,14 +8231,6 @@
 }
 
 /**
- * HWUI renders pipeline type: GL (0) or Vulkan (1).
- */
-enum PipelineType {
-    GL = 0;
-    VULKAN = 1;
-}
-
-/**
  * HWUI stats for a given app.
  */
 message GraphicsStats {
@@ -8251,9 +8243,16 @@
     // The start & end timestamps in UTC as
     // milliseconds since January 1, 1970
     // Compatible with java.util.Date#setTime()
-    optional int64 stats_start = 3;
+    optional int64 start_millis = 3;
 
-    optional int64 stats_end = 4;
+    optional int64 end_millis = 4;
+
+    // HWUI renders pipeline type: GL (1) or Vulkan (2).
+    enum PipelineType {
+        UNKNOWN = 0;
+        GL = 1;
+        VULKAN = 2;
+    }
 
     // HWUI renders pipeline type: GL or Vulkan.
     optional PipelineType pipeline = 5;
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 83d609c..0a138cf 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -960,7 +960,7 @@
             return false;
         }
         List<GestureDescription.GestureStep> steps =
-                MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, 100);
+                MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, 16);
         try {
             synchronized (mLock) {
                 mGestureStatusCallbackSequence++;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 526c0b3..948546b 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -88,6 +88,7 @@
     void createNotificationChannelGroups(String pkg, in ParceledListSlice channelGroupList);
     void createNotificationChannels(String pkg, in ParceledListSlice channelsList);
     void createNotificationChannelsForPackage(String pkg, int uid, in ParceledListSlice channelsList);
+    ParceledListSlice getConversations(boolean onlyImportant);
     ParceledListSlice getConversationsForPackage(String pkg, int uid);
     ParceledListSlice getNotificationChannelGroupsForPackage(String pkg, int uid, boolean includeDeleted);
     NotificationChannelGroup getNotificationChannelGroupForPackage(String groupId, String pkg, int uid);
diff --git a/core/java/android/hardware/display/DeviceProductInfo.java b/core/java/android/hardware/display/DeviceProductInfo.java
new file mode 100644
index 0000000..6ad7fae
--- /dev/null
+++ b/core/java/android/hardware/display/DeviceProductInfo.java
@@ -0,0 +1,224 @@
+/*
+ * 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.hardware.display;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Product-specific information about the display or the directly connected device on the
+ * display chain. For example, if the display is transitively connected, this field may contain
+ * product information about the intermediate device.
+ * @hide
+ */
+public final class DeviceProductInfo implements Parcelable {
+    final private String mName;
+    final private String mManufacturerPnpId;
+    final private String mProductId;
+    final private Integer mModelYear;
+    final private ManufactureDate mManufactureDate;
+
+    public DeviceProductInfo(
+            String name,
+            String manufacturerPnpId,
+            String productCode,
+            Integer modelYear,
+            ManufactureDate manufactureDate) {
+        this.mName = name;
+        this.mManufacturerPnpId = manufacturerPnpId;
+        this.mProductId = productCode;
+        this.mModelYear = modelYear;
+        this.mManufactureDate = manufactureDate;
+    }
+
+    private DeviceProductInfo(Parcel in) {
+        mName = in.readString();
+        mManufacturerPnpId = in.readString();
+        mProductId = (String) in.readValue(null);
+        mModelYear = (Integer) in.readValue(null);
+        mManufactureDate = (ManufactureDate) in.readValue(null);
+    }
+
+    /**
+     * @return Display name.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * @return Manufacturer Plug and Play ID.
+     */
+    public String getManufacturerPnpId() {
+        return mManufacturerPnpId;
+    }
+
+    /**
+     * @return Manufacturer product ID.
+     */
+    public String getProductId() {
+        return mProductId;
+    }
+
+    /**
+     * @return Model year of the device. Typically exactly one of model year or
+     *      manufacture date will be present.
+     */
+    public Integer getModelYear() {
+        return mModelYear;
+    }
+
+    /**
+     * @return Manufacture date. Typically exactly one of model year or manufacture
+     * date will be present.
+     */
+    public ManufactureDate getManufactureDate() {
+        return mManufactureDate;
+    }
+
+    @Override
+    public String toString() {
+        return "DeviceProductInfo{"
+                + "name="
+                + mName
+                + ", manufacturerPnpId="
+                + mManufacturerPnpId
+                + ", productId="
+                + mProductId
+                + ", modelYear="
+                + mModelYear
+                + ", manufactureDate="
+                + mManufactureDate
+                + '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        DeviceProductInfo that = (DeviceProductInfo) o;
+        return Objects.equals(mName, that.mName)
+                && Objects.equals(mManufacturerPnpId, that.mManufacturerPnpId)
+                && Objects.equals(mProductId, that.mProductId)
+                && Objects.equals(mModelYear, that.mModelYear)
+                && Objects.equals(mManufactureDate, that.mManufactureDate);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mName, mManufacturerPnpId, mProductId, mModelYear, mManufactureDate);
+    }
+
+    public static final Creator<DeviceProductInfo> CREATOR =
+            new Creator<DeviceProductInfo>() {
+                @Override
+                public DeviceProductInfo createFromParcel(Parcel in) {
+                    return new DeviceProductInfo(in);
+                }
+
+                @Override
+                public DeviceProductInfo[] newArray(int size) {
+                    return new DeviceProductInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mName);
+        dest.writeString(mManufacturerPnpId);
+        dest.writeValue(mProductId);
+        dest.writeValue(mModelYear);
+        dest.writeValue(mManufactureDate);
+    }
+
+    /**
+     * Stores information about the date of manufacture.
+     *
+     * @hide
+     */
+    public static class ManufactureDate implements Parcelable {
+        final private Integer mWeek;
+        final private Integer mYear;
+
+        public ManufactureDate(Integer week, Integer year) {
+            mWeek = week;
+            mYear = year;
+        }
+
+        protected ManufactureDate(Parcel in) {
+            mWeek = (Integer) in.readValue(null);
+            mYear = (Integer) in.readValue(null);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeValue(mWeek);
+            dest.writeValue(mYear);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final Creator<ManufactureDate> CREATOR =
+                new Creator<ManufactureDate>() {
+                    @Override
+                    public ManufactureDate createFromParcel(Parcel in) {
+                        return new ManufactureDate(in);
+                    }
+
+                    @Override
+                    public ManufactureDate[] newArray(int size) {
+                        return new ManufactureDate[size];
+                    }
+                };
+
+        public int getYear() {
+            return mYear;
+        }
+
+        public int getWeek() {
+            return mWeek;
+        }
+
+        @Override
+        public String toString() {
+            return "ManufactureDate{week=" + mWeek + ", year=" + mYear + '}';
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            ManufactureDate that = (ManufactureDate) o;
+            return Objects.equals(mWeek, that.mWeek) && Objects.equals(mYear, that.mYear);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mWeek, mYear);
+        }
+    }
+}
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index e78aef5..4bd31f8 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -16,6 +16,7 @@
 
 package android.os.storage;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.IVold;
 
@@ -124,4 +125,14 @@
      * legacy storage, {@code false} otherwise.
      */
     public abstract boolean hasLegacyExternalStorage(int uid);
+
+    /**
+     * Makes sure app-private data directories on external storage are setup correctly
+     * after an application is installed or upgraded. The main use for this is OBB dirs,
+     * which can be created/modified by the installer.
+     *
+     * @param packageName the package name of the package
+     * @param uid the uid of the package
+     */
+    public abstract void prepareAppDataAfterInstall(@NonNull String packageName, int uid);
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f86c971..0fa261a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8595,6 +8595,15 @@
         public static final String NOTIFICATION_HISTORY_ENABLED = "notification_history_enabled";
 
         /**
+         * When enabled conversations marked as favorites will be set to bubble.
+         *
+         * The value 1 - enable, 0 - disable
+         * @hide
+         */
+        public static final String BUBBLE_IMPORTANT_CONVERSATIONS
+                = "bubble_important_conversations";
+
+        /**
          * Whether notifications are dismissed by a right-to-left swipe (instead of a left-to-right
          * swipe).
          *
diff --git a/core/java/android/service/notification/ConversationChannelWrapper.java b/core/java/android/service/notification/ConversationChannelWrapper.java
index 9847695..ab465ab 100644
--- a/core/java/android/service/notification/ConversationChannelWrapper.java
+++ b/core/java/android/service/notification/ConversationChannelWrapper.java
@@ -33,6 +33,8 @@
     private CharSequence mGroupLabel;
     private CharSequence mParentChannelLabel;
     private ShortcutInfo mShortcutInfo;
+    private String mPkg;
+    private int mUid;
 
     public ConversationChannelWrapper() {}
 
@@ -41,6 +43,8 @@
         mGroupLabel = in.readCharSequence();
         mParentChannelLabel = in.readCharSequence();
         mShortcutInfo = in.readParcelable(ShortcutInfo.class.getClassLoader());
+        mPkg = in.readStringNoHelper();
+        mUid = in.readInt();
     }
 
     @Override
@@ -49,6 +53,8 @@
         dest.writeCharSequence(mGroupLabel);
         dest.writeCharSequence(mParentChannelLabel);
         dest.writeParcelable(mShortcutInfo, flags);
+        dest.writeStringNoHelper(mPkg);
+        dest.writeInt(mUid);
     }
 
     @Override
@@ -103,6 +109,22 @@
         mShortcutInfo = shortcutInfo;
     }
 
+    public String getPkg() {
+        return mPkg;
+    }
+
+    public void setPkg(String pkg) {
+        mPkg = pkg;
+    }
+
+    public int getUid() {
+        return mUid;
+    }
+
+    public void setUid(int uid) {
+        mUid = uid;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -111,12 +133,14 @@
         return Objects.equals(getNotificationChannel(), that.getNotificationChannel()) &&
                 Objects.equals(getGroupLabel(), that.getGroupLabel()) &&
                 Objects.equals(getParentChannelLabel(), that.getParentChannelLabel()) &&
-                Objects.equals(getShortcutInfo(), that.getShortcutInfo());
+                Objects.equals(getShortcutInfo(), that.getShortcutInfo()) &&
+                Objects.equals(getPkg(), that.getPkg()) &&
+                getUid() == that.getUid();
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(getNotificationChannel(), getGroupLabel(), getParentChannelLabel(),
-                getShortcutInfo());
+                getShortcutInfo(), getPkg(), getUid());
     }
 }
diff --git a/core/java/android/view/CutoutSpecification.java b/core/java/android/view/CutoutSpecification.java
index d21a952..850e9fc 100644
--- a/core/java/android/view/CutoutSpecification.java
+++ b/core/java/android/view/CutoutSpecification.java
@@ -406,9 +406,7 @@
                     }
                     currentIndex += RIGHT_MARKER.length();
                 } else if (specWithoutDp.startsWith(BOTTOM_MARKER, currentIndex)) {
-                    if (!mPositionFromCenterVertical) {
-                        parseSvgPathSpec(region, sb.toString());
-                    }
+                    parseSvgPathSpec(region, sb.toString());
                     currentIndex += BOTTOM_MARKER.length();
 
                     /* prepare to parse the rest path */
@@ -416,9 +414,7 @@
                     mBindBottomCutout = true;
                     mPositionFromBottom = true;
                 } else if (specWithoutDp.startsWith(CENTER_VERTICAL_MARKER, currentIndex)) {
-                    if (!mPositionFromBottom) {
-                        parseSvgPathSpec(region, sb.toString());
-                    }
+                    parseSvgPathSpec(region, sb.toString());
                     currentIndex += CENTER_VERTICAL_MARKER.length();
 
                     /* prepare to parse the rest path */
@@ -431,14 +427,16 @@
                     /* prepare to parse the rest path */
                     resetStatus(sb);
                 } else if (specWithoutDp.startsWith(BIND_LEFT_CUTOUT_MARKER, currentIndex)) {
-                    if (!mBindBottomCutout && !mBindRightCutout) {
-                        mBindLeftCutout = true;
-                    }
+                    mBindBottomCutout = false;
+                    mBindRightCutout = false;
+                    mBindLeftCutout = true;
+
                     currentIndex += BIND_LEFT_CUTOUT_MARKER.length();
                 } else if (specWithoutDp.startsWith(BIND_RIGHT_CUTOUT_MARKER, currentIndex)) {
-                    if (!mBindBottomCutout && !mBindLeftCutout) {
-                        mBindRightCutout = true;
-                    }
+                    mBindBottomCutout = false;
+                    mBindLeftCutout = false;
+                    mBindRightCutout = true;
+
                     currentIndex += BIND_RIGHT_CUTOUT_MARKER.length();
                 } else {
                     currentIndex += 1;
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index b9868a7..3047385 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -28,6 +28,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.hardware.display.DeviceProductInfo;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -70,6 +71,13 @@
     public DisplayAddress address;
 
     /**
+     * Product-specific information about the display or the directly connected device on the
+     * display chain. For example, if the display is transitively connected, this field may contain
+     * product information about the intermediate device.
+     */
+    public DeviceProductInfo deviceProductInfo;
+
+    /**
      * The human-readable name of the display.
      */
     public String name;
@@ -297,6 +305,7 @@
                 && type == other.type
                 && displayId == other.displayId
                 && Objects.equals(address, other.address)
+                && Objects.equals(deviceProductInfo, other.deviceProductInfo)
                 && Objects.equals(uniqueId, other.uniqueId)
                 && appWidth == other.appWidth
                 && appHeight == other.appHeight
@@ -336,6 +345,7 @@
         type = other.type;
         displayId = other.displayId;
         address = other.address;
+        deviceProductInfo = other.deviceProductInfo;
         name = other.name;
         uniqueId = other.uniqueId;
         appWidth = other.appWidth;
@@ -373,6 +383,7 @@
         type = source.readInt();
         displayId = source.readInt();
         address = source.readParcelable(null);
+        deviceProductInfo = source.readParcelable(null);
         name = source.readString();
         appWidth = source.readInt();
         appHeight = source.readInt();
@@ -418,6 +429,7 @@
         dest.writeInt(type);
         dest.writeInt(displayId);
         dest.writeParcelable(address, flags);
+        dest.writeParcelable(deviceProductInfo, flags);
         dest.writeString(name);
         dest.writeInt(appWidth);
         dest.writeInt(appHeight);
@@ -645,6 +657,8 @@
         if (address != null) {
             sb.append(", address ").append(address);
         }
+        sb.append(", deviceProductInfo ");
+        sb.append(deviceProductInfo);
         sb.append(", state ");
         sb.append(Display.stateToString(state));
         if (ownerUid != 0 || ownerPackageName != null) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cf48c52..29371b0 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -41,6 +41,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
 import android.os.Build;
@@ -1286,12 +1287,14 @@
         public boolean isInternal;
         public float density;
         public boolean secure;
+        public DeviceProductInfo deviceProductInfo;
 
         @Override
         public String toString() {
             return "DisplayInfo{isInternal=" + isInternal
                     + ", density=" + density
-                    + ", secure=" + secure + "}";
+                    + ", secure=" + secure
+                    + ", deviceProductInfo=" + deviceProductInfo + "}";
         }
     }
 
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 61d22d1..a201a33 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -3100,7 +3100,7 @@
             final ViewGroup viewGroup = (ViewGroup) holder.itemView;
             int start = getListPosition(position);
             int startType = getRowType(start);
-            if (viewGroup.getForeground() == null) {
+            if (viewGroup.getForeground() == null && position > 0) {
                 viewGroup.setForeground(
                         getResources().getDrawable(R.drawable.chooser_row_layer_list, null));
             }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f168215..96bfe73 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1566,6 +1566,7 @@
         viewPager.setVisibility(View.VISIBLE);
         tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
         mMultiProfilePagerAdapter.setOnProfileSelectedListener(tabHost::setCurrentTab);
+        findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE);
     }
 
     private void resetTabsHeaderStyle(TabWidget tabWidget) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 3f81b11..741ce82 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -37,6 +37,7 @@
 #include <stdio.h>
 #include <system/graphics.h>
 #include <ui/ConfigStoreTypes.h>
+#include <ui/DeviceProductInfo.h>
 #include <ui/DisplayConfig.h>
 #include <ui/DisplayInfo.h>
 #include <ui/DisplayedFrameStats.h>
@@ -65,9 +66,19 @@
 static struct {
     jclass clazz;
     jmethodID ctor;
+} gIntegerClassInfo;
+
+static jobject toInteger(JNIEnv* env, int32_t i) {
+    return env->NewObject(gIntegerClassInfo.clazz, gIntegerClassInfo.ctor, i);
+}
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
     jfieldID isInternal;
     jfieldID density;
     jfieldID secure;
+    jfieldID deviceProductInfo;
 } gDisplayInfoClassInfo;
 
 static struct {
@@ -111,6 +122,16 @@
 
 static struct {
     jclass clazz;
+    jmethodID ctor;
+} gDeviceProductInfoClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gDeviceProductInfoManufactureDateClassInfo;
+
+static struct {
+    jclass clazz;
     jmethodID builder;
 } gGraphicBufferClassInfo;
 
@@ -773,6 +794,41 @@
     }
 }
 
+static jobject convertDeviceProductInfoToJavaObject(
+        JNIEnv* env, const std::optional<DeviceProductInfo>& info) {
+    using ModelYear = android::DeviceProductInfo::ModelYear;
+    using ManufactureYear = android::DeviceProductInfo::ManufactureYear;
+    using ManufactureWeekAndYear = android::DeviceProductInfo::ManufactureWeekAndYear;
+
+    if (!info) return nullptr;
+    jstring name = env->NewStringUTF(info->name.data());
+    jstring manufacturerPnpId = env->NewStringUTF(info->manufacturerPnpId.data());
+    jobject productId = env->NewStringUTF(info->productId.data());
+    const auto& date = info->manufactureOrModelDate;
+    jobject modelYear, manufactureDate;
+    if (const auto* model = std::get_if<ModelYear>(&date)) {
+        modelYear = toInteger(env, model->year);
+        manufactureDate = nullptr;
+    } else if (const auto* manufactureWeekAndYear = std::get_if<ManufactureWeekAndYear>(&date)) {
+        modelYear = nullptr;
+        manufactureDate = env->NewObject(gDeviceProductInfoManufactureDateClassInfo.clazz,
+                                               gDeviceProductInfoManufactureDateClassInfo.ctor,
+                                               toInteger(env, manufactureWeekAndYear->week),
+                                               toInteger(env, manufactureWeekAndYear->year));
+    } else if (const auto* manufactureYear = std::get_if<ManufactureYear>(&date)) {
+        modelYear = nullptr;
+        manufactureDate = env->NewObject(gDeviceProductInfoManufactureDateClassInfo.clazz,
+                                       gDeviceProductInfoManufactureDateClassInfo.ctor,
+                                       nullptr,
+                                       toInteger(env, manufactureYear->year));
+    } else {
+        LOG_FATAL("Unknown alternative for variant DeviceProductInfo::ManufactureOrModelDate");
+    }
+
+    return env->NewObject(gDeviceProductInfoClassInfo.clazz, gDeviceProductInfoClassInfo.ctor, name,
+                          manufacturerPnpId, productId, modelYear, manufactureDate);
+}
+
 static jobject nativeGetDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
     DisplayInfo info;
     if (const auto token = ibinderForJavaObject(env, tokenObj);
@@ -785,6 +841,8 @@
                          info.connectionType == DisplayConnectionType::Internal);
     env->SetFloatField(object, gDisplayInfoClassInfo.density, info.density);
     env->SetBooleanField(object, gDisplayInfoClassInfo.secure, info.secure);
+    env->SetObjectField(object, gDisplayInfoClassInfo.deviceProductInfo,
+                        convertDeviceProductInfoToJavaObject(env, info.deviceProductInfo));
     return object;
 }
 
@@ -1527,12 +1585,19 @@
     int err = RegisterMethodsOrDie(env, "android/view/SurfaceControl",
             sSurfaceControlMethods, NELEM(sSurfaceControlMethods));
 
+    jclass integerClass = FindClassOrDie(env, "java/lang/Integer");
+    gIntegerClassInfo.clazz = MakeGlobalRefOrDie(env, integerClass);
+    gIntegerClassInfo.ctor = GetMethodIDOrDie(env, gIntegerClassInfo.clazz, "<init>", "(I)V");
+
     jclass infoClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayInfo");
     gDisplayInfoClassInfo.clazz = MakeGlobalRefOrDie(env, infoClazz);
     gDisplayInfoClassInfo.ctor = GetMethodIDOrDie(env, infoClazz, "<init>", "()V");
     gDisplayInfoClassInfo.isInternal = GetFieldIDOrDie(env, infoClazz, "isInternal", "Z");
     gDisplayInfoClassInfo.density = GetFieldIDOrDie(env, infoClazz, "density", "F");
     gDisplayInfoClassInfo.secure = GetFieldIDOrDie(env, infoClazz, "secure", "Z");
+    gDisplayInfoClassInfo.deviceProductInfo =
+            GetFieldIDOrDie(env, infoClazz, "deviceProductInfo",
+                            "Landroid/hardware/display/DeviceProductInfo;");
 
     jclass configClazz = FindClassOrDie(env, "android/view/SurfaceControl$DisplayConfig");
     gDisplayConfigClassInfo.clazz = MakeGlobalRefOrDie(env, configClazz);
@@ -1573,6 +1638,25 @@
     gHdrCapabilitiesClassInfo.ctor = GetMethodIDOrDie(env, hdrCapabilitiesClazz, "<init>",
             "([IFFF)V");
 
+    jclass deviceProductInfoClazz =
+            FindClassOrDie(env, "android/hardware/display/DeviceProductInfo");
+    gDeviceProductInfoClassInfo.clazz = MakeGlobalRefOrDie(env, deviceProductInfoClazz);
+    gDeviceProductInfoClassInfo.ctor =
+            GetMethodIDOrDie(env, deviceProductInfoClazz, "<init>",
+                             "(Ljava/lang/String;"
+                             "Ljava/lang/String;"
+                             "Ljava/lang/String;"
+                             "Ljava/lang/Integer;"
+                             "Landroid/hardware/display/DeviceProductInfo$ManufactureDate;)V");
+
+    jclass deviceProductInfoManufactureDateClazz =
+            FindClassOrDie(env, "android/hardware/display/DeviceProductInfo$ManufactureDate");
+    gDeviceProductInfoManufactureDateClassInfo.clazz =
+            MakeGlobalRefOrDie(env, deviceProductInfoManufactureDateClazz);
+    gDeviceProductInfoManufactureDateClassInfo.ctor =
+            GetMethodIDOrDie(env, deviceProductInfoManufactureDateClazz, "<init>",
+                             "(Ljava/lang/Integer;Ljava/lang/Integer;)V");
+
     jclass graphicsBufferClazz = FindClassOrDie(env, "android/graphics/GraphicBuffer");
     gGraphicBufferClassInfo.clazz = MakeGlobalRefOrDie(env, graphicsBufferClazz);
     gGraphicBufferClassInfo.builder = GetStaticMethodIDOrDie(env, graphicsBufferClazz,
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 2de5b7f..bb654f0 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -33,8 +33,9 @@
 
 message GraphicsStatsProto {
     enum PipelineType {
-        GL = 0;
-        VULKAN = 1;
+        UNKNOWN = 0;
+        GL = 1;
+        VULKAN = 2;
     }
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 488698a..6530036 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4953,16 +4953,6 @@
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|appPredictor" />
 
-    <!-- Feature Id for Country Detector. -->
-    <feature android:featureId="CountryDetector" android:label="@string/country_detector"/>
-    <!-- Feature Id for Location service. -->
-    <feature android:featureId="LocationService" android:label="@string/location_service"/>
-    <!-- Feature Id for Sensor Notification service. -->
-    <feature android:featureId="SensorNotificationService"
-             android:label="@string/sensor_notification_service"/>
-    <!-- Feature Id for Twilight service. -->
-    <feature android:featureId="TwilightService" android:label="@string/twilight_service"/>
-
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index 24a21eb..c0de693 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -78,6 +78,14 @@
                 android:layout_height="wrap_content"
                 android:visibility="gone">
             </TabWidget>
+            <View
+                android:id="@+id/resolver_tab_divider"
+                android:visibility="gone"
+                android:layout_alwaysShow="true"
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="?attr/colorBackgroundFloating"
+                android:foreground="?attr/dividerVertical" />
             <FrameLayout
                 android:id="@android:id/tabcontent"
                 android:layout_width="match_parent"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index b4e6286..4359b10 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -94,6 +94,13 @@
                 android:layout_height="wrap_content"
                 android:visibility="gone">
             </TabWidget>
+            <View
+                android:id="@+id/resolver_tab_divider"
+                android:visibility="gone"
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="?attr/colorBackgroundFloating"
+                android:foreground="?attr/dividerVertical" />
             <FrameLayout
                 android:id="@android:id/tabcontent"
                 android:layout_width="match_parent"
@@ -102,10 +109,7 @@
                     android:id="@+id/profile_pager"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:divider="?attr/dividerVertical"
-                    android:footerDividersEnabled="false"
-                    android:headerDividersEnabled="false"
-                    android:dividerHeight="1dp"/>
+                    android:minHeight="@dimen/resolver_empty_state_height" />
             </FrameLayout>
         </LinearLayout>
     </TabHost>
diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml
index d481eff..c4f9ed9 100644
--- a/core/res/res/layout/resolver_list_per_profile.xml
+++ b/core/res/res/layout/resolver_list_per_profile.xml
@@ -29,7 +29,6 @@
         android:elevation="@dimen/resolver_elevation"
         android:nestedScrollingEnabled="true"
         android:scrollbarStyle="outsideOverlay"
-        android:scrollIndicators="top|bottom"
         android:divider="@null"
         android:footerDividersEnabled="false"
         android:headerDividersEnabled="false"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index b546738..72e8b0c 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -175,6 +175,14 @@
                 android:layout_height="wrap_content"
                 android:visibility="gone">
             </TabWidget>
+            <View
+                android:id="@+id/resolver_tab_divider"
+                android:visibility="gone"
+                android:layout_alwaysShow="true"
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="?attr/colorBackgroundFloating"
+                android:foreground="?attr/dividerVertical" />
             <FrameLayout
                 android:id="@android:id/tabcontent"
                 android:layout_width="match_parent"
@@ -183,10 +191,7 @@
                     android:id="@+id/profile_pager"
                     android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:dividerHeight="1dp"
-                    android:divider="?attr/dividerVertical"
-                    android:footerDividersEnabled="false"
-                    android:headerDividersEnabled="false"/>
+                    android:minHeight="@dimen/resolver_empty_state_height" />
             </FrameLayout>
         </LinearLayout>
     </TabHost>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e7ad8eb..e6a93e5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -421,14 +421,6 @@
          [CHAR LIMIT=NONE] -->
     <string name="location_changed_notification_text">Tap to see your location settings.</string>
 
-    <!-- Feature Id for Country Detector. [CHAR LIMIT=NONE]-->
-    <string name="country_detector">Country Detector</string>
-    <!-- Feature Id for Location service. [CHAR LIMIT=NONE]-->
-    <string name="location_service">Location Service</string>
-    <!-- Feature Id for Sensor Notification service. [CHAR LIMIT=NONE]-->
-    <string name="sensor_notification_service">Sensor Notification Service</string>
-    <!-- Feature Id for Twilight service. [CHAR LIMIT=NONE]-->
-    <string name="twilight_service">Twilight Service</string>
 
     <!-- Factory reset warning dialog strings--> <skip />
     <!-- Shows up in the dialog's title to warn about an impeding factory reset. [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b8dd418..85c2a2a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3874,6 +3874,7 @@
   <java-symbol type="id" name="resolver_empty_state_title" />
   <java-symbol type="id" name="resolver_empty_state_subtitle" />
   <java-symbol type="id" name="resolver_empty_state_button" />
+  <java-symbol type="id" name="resolver_tab_divider" />
   <java-symbol type="string" name="resolver_cant_share_with_work_apps" />
   <java-symbol type="string" name="resolver_cant_share_with_personal_apps" />
   <java-symbol type="string" name="resolver_cant_share_cross_profile_explanation" />
diff --git a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
index 1f831bb..b41f90c 100644
--- a/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
+++ b/core/tests/coretests/src/android/view/CutoutSpecificationTest.java
@@ -69,6 +69,13 @@
             + "z\n"
             + "@right\n"
             + "@bind_right_cutout\n"
+            + "@bottom\n"
+            + "M 0,0\n"
+            + "h -24\n"
+            + "v -48\n"
+            + "h 48\n"
+            + "v 48\n"
+            + "z\n"
             + "@dp";
     private static final String CORNER_CUTOUT_SPECIFICATION = "M 0,0\n"
             + "h 1\n"
@@ -141,13 +148,66 @@
     }
 
     @Test
+    public void parse_withBindMarker_shouldHaveTopBound() {
+        CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getTopBound()).isEqualTo(new Rect(0, 0, 168, 168));
+    }
+
+    @Test
     public void parse_withBindMarker_shouldHaveRightBound() {
         CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
         assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(912, 960, 1080, 1128));
     }
 
     @Test
-    public void parse_tallCutout_shouldBeDone() {
+    public void parse_withBindMarker_shouldHaveBottomBound() {
+        CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getBottomBound()).isEqualTo(new Rect(456, 1752, 624, 1920));
+    }
+
+    @Test
+    public void parse_withBindMarker_shouldMatchExpectedSafeInset() {
+        CutoutSpecification cutoutSpecification = mParser.parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(168, 168, 168, 168));
+    }
+
+    @Test
+    public void parse_withBindMarker_tabletLikeDevice_shouldHaveLeftBound() {
+        CutoutSpecification cutoutSpecification = new CutoutSpecification.Parser(3.5f, 1920, 1080)
+                .parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getLeftBound()).isEqualTo(new Rect(0, 540, 168, 708));
+    }
+
+    @Test
+    public void parse_withBindMarker_tabletLikeDevice_shouldHaveTopBound() {
+        CutoutSpecification cutoutSpecification = new CutoutSpecification.Parser(3.5f, 1920, 1080)
+                .parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getTopBound()).isEqualTo(new Rect(0, 0, 168, 168));
+    }
+
+    @Test
+    public void parse_withBindMarker_tabletLikeDevice_shouldHaveRightBound() {
+        CutoutSpecification cutoutSpecification = new CutoutSpecification.Parser(3.5f, 1920, 1080)
+                .parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getRightBound()).isEqualTo(new Rect(1752, 540, 1920, 708));
+    }
+
+    @Test
+    public void parse_withBindMarker_tabletLikeDevice_shouldHaveBottomBound() {
+        CutoutSpecification cutoutSpecification = new CutoutSpecification.Parser(3.5f, 1920, 1080)
+                .parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getBottomBound()).isEqualTo(new Rect(876, 912, 1044, 1080));
+    }
+
+    @Test
+    public void parse_withBindMarker_tabletLikeDevice_shouldMatchExpectedSafeInset() {
+        CutoutSpecification cutoutSpecification = new CutoutSpecification.Parser(3.5f, 1920, 1080)
+                .parse(WITH_BIND_CUTOUT_SPECIFICATION);
+        assertThat(cutoutSpecification.getSafeInset()).isEqualTo(new Rect(168, 0, 168, 168));
+    }
+
+    @Test
+    public void parse_tallCutout_topBoundShouldMatchExpectedHeight() {
         CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
                 + "L -48, 0\n"
                 + "L -44.3940446283, 36.0595537175\n"
@@ -162,7 +222,7 @@
     }
 
     @Test
-    public void parse_wideCutout_shouldBeDone() {
+    public void parse_wideCutout_topBoundShouldMatchExpectedWidth() {
         CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
                 + "L -72, 0\n"
                 + "L -69.9940446283, 20.0595537175\n"
@@ -177,7 +237,7 @@
     }
 
     @Test
-    public void parse_narrowCutout_shouldBeDone() {
+    public void parse_narrowCutout_topBoundShouldHaveExpectedWidth() {
         CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
                 + "L -24, 0\n"
                 + "L -21.9940446283, 20.0595537175\n"
@@ -192,7 +252,7 @@
     }
 
     @Test
-    public void parse_doubleCutout_shouldBeDone() {
+    public void parse_doubleCutout_topBoundShouldHaveExpectedHeight() {
         CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
                 + "L -72, 0\n"
                 + "L -69.9940446283, 20.0595537175\n"
@@ -217,7 +277,7 @@
     }
 
     @Test
-    public void parse_cornerCutout_shouldBeDone() {
+    public void parse_cornerCutout_topBoundShouldHaveExpectedHeight() {
         CutoutSpecification cutoutSpecification = mParser.parse("M 0,0\n"
                 + "L -48, 0\n"
                 + "C -48,48 -48,48 0,48\n"
@@ -229,7 +289,7 @@
     }
 
     @Test
-    public void parse_holeCutout_shouldBeDone() {
+    public void parse_holeCutout_shouldMatchExpectedInset() {
         CutoutSpecification cutoutSpecification = mParser.parse("M 20.0,20.0\n"
                 + "h 136\n"
                 + "v 136\n"
@@ -259,4 +319,38 @@
         assertThat(cutoutSpecification.getSafeInset())
                 .isEqualTo(new Rect(6, 0, 8, 0));
     }
+
+    @Test
+    public void parse_bottomLeftSpec_withBindLeftMarker_shouldBeLeftBound() {
+        CutoutSpecification cutoutSpecification =
+                new CutoutSpecification.Parser(2f, 400, 200)
+                        .parse("@bottom"
+                                + "M 0,0\n"
+                                + "v -10\n"
+                                + "h 10\n"
+                                + "v 10\n"
+                                + "z\n"
+                                + "@left\n"
+                                + "@bind_left_cutout");
+
+        assertThat(cutoutSpecification.getLeftBound())
+                .isEqualTo(new Rect(0, 190, 10, 200));
+    }
+
+    @Test
+    public void parse_bottomRightSpec_withBindRightMarker_shouldBeRightBound() {
+        CutoutSpecification cutoutSpecification =
+                new CutoutSpecification.Parser(2f, 400, 200)
+                        .parse("@bottom"
+                                + "M 0,0\n"
+                                + "v -10\n"
+                                + "h 10\n"
+                                + "v 10\n"
+                                + "z\n"
+                                + "@right\n"
+                                + "@bind_right_cutout");
+
+        assertThat(cutoutSpecification.getRightBound())
+                .isEqualTo(new Rect(390, 190, 400, 200));
+    }
 }
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index dd5676c..745393c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -30,8 +30,9 @@
 
 message GraphicsStatsProto {
     enum PipelineType {
-        GL = 0;
-        VULKAN = 1;
+        UNKNOWN = 0;
+        GL = 1;
+        VULKAN = 2;
     }
 
     // The package name of the app
diff --git a/packages/SystemUI/res/layout/qs_carrier.xml b/packages/SystemUI/res/layout/qs_carrier.xml
index 28b2d21..a5b8cfa 100644
--- a/packages/SystemUI/res/layout/qs_carrier.xml
+++ b/packages/SystemUI/res/layout/qs_carrier.xml
@@ -14,7 +14,7 @@
   ~ limitations under the License
   -->
 
-<com.android.systemui.qs.QSCarrier
+<com.android.systemui.qs.carrier.QSCarrier
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/linear_carrier"
     android:layout_width="wrap_content"
@@ -46,4 +46,4 @@
         android:singleLine="true"
         android:maxEms="7"/>
 
-</com.android.systemui.qs.QSCarrier>
\ No newline at end of file
+</com.android.systemui.qs.carrier.QSCarrier>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_carrier_group.xml b/packages/SystemUI/res/layout/qs_carrier_group.xml
index f2b0606..fd53a8b 100644
--- a/packages/SystemUI/res/layout/qs_carrier_group.xml
+++ b/packages/SystemUI/res/layout/qs_carrier_group.xml
@@ -15,7 +15,7 @@
   -->
 
 <!-- Extends LinearLayout -->
-<com.android.systemui.qs.QSCarrierGroup
+<com.android.systemui.qs.carrier.QSCarrierGroup
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/qs_mobile"
     android:layout_width="0dp"
@@ -71,4 +71,4 @@
         android:layout_weight="1"
         android:visibility="gone"/>
 
-</com.android.systemui.qs.QSCarrierGroup>
\ No newline at end of file
+</com.android.systemui.qs.carrier.QSCarrierGroup>
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 8cd70cf..d422dd7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -60,6 +60,7 @@
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.qs.QSDetail.Callback;
+import com.android.systemui.qs.carrier.QSCarrierGroup;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
@@ -435,23 +436,22 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        // Handle padding of SystemIconsView
         DisplayCutout cutout = insets.getDisplayCutout();
-
-        // Handle padding of QuickStatusBarHeader
         Pair<Integer, Integer> cornerCutoutPadding = StatusBarWindowView.cornerCutoutMargins(
                 cutout, getDisplay());
         Pair<Integer, Integer> padding =
                 StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
                         cutout, cornerCutoutPadding, mRoundedCornerPadding);
-        setPadding(padding.first, 0, padding.second, getPaddingBottom());
-
-        // Handle padding of SystemIconsView
         final int waterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top;
-        mSystemIconsView.setPaddingRelative(
-                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start),
-                waterfallTopInset,
-                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
-                0);
+        int statusBarPaddingLeft = isLayoutRtl()
+                ? getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end)
+                : getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start);
+        int statusBarPaddingRight = isLayoutRtl()
+                ? getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start)
+                : getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end);
+        mSystemIconsView.setPadding(padding.first + statusBarPaddingLeft, waterfallTopInset,
+                padding.second + statusBarPaddingRight, 0);
 
         return super.onApplyWindowInsets(insets);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 867677a..d899acb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.qs;
 
 import com.android.systemui.R;
+import com.android.systemui.qs.carrier.QSCarrierGroupController;
 
 import javax.inject.Inject;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt b/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
new file mode 100644
index 0000000..663f3f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.qs.carrier
+
+/**
+ * Represents the state of cell signal for a particular slot.
+ *
+ * To be used between [QSCarrierGroupController] and [QSCarrier].
+ */
+data class CellSignalState(
+    @JvmField val visible: Boolean = false,
+    @JvmField val mobileSignalIconId: Int = 0,
+    @JvmField val contentDescription: String? = null,
+    @JvmField val typeContentDescription: String? = null,
+    @JvmField val roaming: Boolean = false
+) {
+    /**
+     * Changes the visibility of this state by returning a copy with the visibility changed.
+     *
+     * If the visibility would not change, the same state is returned.
+     *
+     * @param visible the new visibility state
+     * @return `this` if `this.visible == visible`. Else, a new copy with the visibility changed.
+     */
+    fun changeVisibility(visible: Boolean): CellSignalState {
+        if (this.visible == visible) return this
+        else return copy(visible = visible)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
rename to packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 5a9c360..ad275f1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs;
+package com.android.systemui.qs.carrier;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -29,6 +29,7 @@
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
+import com.android.systemui.qs.QuickStatusBarHeader;
 
 import java.util.Objects;
 
@@ -41,7 +42,7 @@
     private DualToneHandler mDualToneHandler;
     private ColorStateList mColorForegroundStateList;
     private float mColorForegroundIntensity;
-    private QSCarrierGroupController.CellSignalState mLastSignalState;
+    private CellSignalState mLastSignalState;
 
     public QSCarrier(Context context) {
         super(context);
@@ -76,8 +77,13 @@
         mColorForegroundIntensity = QuickStatusBarHeader.getColorIntensity(colorForeground);
     }
 
-    public void updateState(QSCarrierGroupController.CellSignalState state) {
-        if (Objects.equals(state, mLastSignalState)) return;
+    /**
+     * Update the state of this view
+     * @param state the current state of the signal for this view
+     * @return true if the state was actually changed
+     */
+    public boolean updateState(CellSignalState state) {
+        if (Objects.equals(state, mLastSignalState)) return false;
         mLastSignalState = state;
         mMobileGroup.setVisibility(state.visible ? View.VISIBLE : View.GONE);
         if (state.visible) {
@@ -103,6 +109,7 @@
             }
             mMobileSignal.setContentDescription(contentDescription);
         }
+        return true;
     }
 
     private boolean hasValidTypeContentDescription(String typeContentDescription) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
rename to packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java
index 346c75d..d03563f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs;
+package com.android.systemui.qs.carrier;
 
 import android.content.Context;
 import android.util.AttributeSet;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java
rename to packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
index eb5b4cc..f9b1473 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs;
+package com.android.systemui.qs.carrier;
 
 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
 
@@ -29,7 +29,6 @@
 import android.view.View;
 import android.widget.TextView;
 
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.keyguard.CarrierTextController;
@@ -38,7 +37,6 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.NetworkController;
 
-import java.util.Objects;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -82,11 +80,13 @@
                         Log.e(TAG, "Invalid SIM slot index for subscription: " + subId);
                         return;
                     }
-                    mInfos[slotIndex].visible = statusIcon.visible;
-                    mInfos[slotIndex].mobileSignalIconId = statusIcon.icon;
-                    mInfos[slotIndex].contentDescription = statusIcon.contentDescription;
-                    mInfos[slotIndex].typeContentDescription = typeContentDescription.toString();
-                    mInfos[slotIndex].roaming = roaming;
+                    mInfos[slotIndex] = new CellSignalState(
+                            statusIcon.visible,
+                            statusIcon.icon,
+                            statusIcon.contentDescription,
+                            typeContentDescription.toString(),
+                            roaming
+                    );
                     mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
                 }
 
@@ -94,7 +94,7 @@
                 public void setNoSims(boolean hasNoSims, boolean simDetected) {
                     if (hasNoSims) {
                         for (int i = 0; i < SIM_SLOTS; i++) {
-                            mInfos[i].visible = false;
+                            mInfos[i] = mInfos[i].changeVisibility(false);
                         }
                     }
                     mMainHandler.obtainMessage(H.MSG_UPDATE_STATE).sendToTarget();
@@ -236,7 +236,7 @@
                                         + info.subscriptionIds[i]);
                         continue;
                     }
-                    mInfos[slot].visible = true;
+                    mInfos[slot] = mInfos[slot].changeVisibility(true);
                     slotSeen[slot] = true;
                     mCarrierGroups[slot].setCarrierText(
                             info.listOfCarriers[i].toString().trim());
@@ -244,7 +244,7 @@
                 }
                 for (int i = 0; i < SIM_SLOTS; i++) {
                     if (!slotSeen[i]) {
-                        mInfos[i].visible = false;
+                        mInfos[i] = mInfos[i].changeVisibility(false);
                         mCarrierGroups[i].setVisibility(View.GONE);
                     }
                 }
@@ -255,7 +255,7 @@
             // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
             // info.carrierText in a different view.
             for (int i = 0; i < SIM_SLOTS; i++) {
-                mInfos[i].visible = false;
+                mInfos[i] = mInfos[i].changeVisibility(false);
                 mCarrierGroups[i].setCarrierText("");
                 mCarrierGroups[i].setVisibility(View.GONE);
             }
@@ -295,35 +295,6 @@
         }
     }
 
-    static final class CellSignalState {
-        boolean visible;
-        int mobileSignalIconId;
-        String contentDescription;
-        String typeContentDescription;
-        boolean roaming;
-
-        @Override
-        public boolean equals(@Nullable Object obj) {
-            if (this == obj) return true;
-            if (!(obj instanceof CellSignalState)) return false;
-            CellSignalState other = (CellSignalState) obj;
-            return this.visible == other.visible
-                    && this.mobileSignalIconId == other.mobileSignalIconId
-                    && Objects.equals(this.contentDescription, other.contentDescription)
-                    && Objects.equals(this.typeContentDescription, other.typeContentDescription)
-                    && this.roaming == other.roaming;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(visible,
-                    mobileSignalIconId,
-                    contentDescription,
-                    typeContentDescription,
-                    roaming);
-        }
-    }
-
     public static class Builder {
         private QSCarrierGroup mView;
         private final ActivityStarter mActivityStarter;
@@ -343,7 +314,7 @@
             mCarrierTextControllerBuilder = carrierTextControllerBuilder;
         }
 
-        Builder setQSCarrierGroup(QSCarrierGroup view) {
+        public Builder setQSCarrierGroup(QSCarrierGroup view) {
             mView = view;
             return this;
         }
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 248e5fe..60eda06 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,7 @@
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -112,7 +113,8 @@
     boolean mSkipPost = false;
 
     @Retention(SOURCE)
-    @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE})
+    @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
+            ACTION_UNBUBBLE, ACTION_SETTINGS})
     private @interface Action {}
     static final int ACTION_BUBBLE = 0;
     static final int ACTION_HOME = 1;
@@ -128,8 +130,6 @@
             mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
         } else {
             mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
-            Settings.Global.putInt(
-                    mContext.getContentResolver(), Settings.Global.NOTIFICATION_BUBBLES, 1);
         }
         closeControls(v, true);
     };
@@ -225,7 +225,9 @@
             mShortcutInfo = shortcuts.get(0);
         }
 
-        mIsBubbleable = mEntry.getBubbleMetadata() != null;
+        mIsBubbleable = mEntry.getBubbleMetadata() != null
+            && Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.NOTIFICATION_BUBBLES, 0) == 1;
         mStartedAsBubble = mEntry.isBubble();
 
         createConversationChannelIfNeeded();
@@ -382,6 +384,11 @@
         ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
     }
 
+    private boolean bubbleImportantConversations() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                BUBBLE_IMPORTANT_CONVERSATIONS, 1) == 1;
+    }
+
     private void bindDelegate() {
         TextView delegateView = findViewById(R.id.delegate_name);
 
@@ -579,6 +586,10 @@
                     case ACTION_FAVORITE:
                         mChannelToUpdate.setImportantConversation(
                                 !mChannelToUpdate.isImportantConversation());
+                        if (mChannelToUpdate.isImportantConversation()
+                                && bubbleImportantConversations()) {
+                            mChannelToUpdate.setAllowBubbles(true);
+                        }
                         break;
                     case ACTION_MUTE:
                         if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt
new file mode 100644
index 0000000..75be74b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.qs.carrier
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertNotSame
+import org.junit.Assert.assertSame
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class CellSignalStateTest : SysuiTestCase() {
+
+    @Test
+    fun testChangeVisibility_sameObject() {
+        val c = CellSignalState()
+
+        val other = c.changeVisibility(c.visible)
+
+        assertSame(c, other)
+    }
+
+    @Test
+    fun testChangeVisibility_otherObject() {
+        val c = CellSignalState()
+
+        val other = c.changeVisibility(!c.visible)
+
+        assertNotSame(c, other)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
index 715087d..fa02231 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs;
+package com.android.systemui.qs.carrier;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
new file mode 100644
index 0000000..022dc84
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.qs.carrier;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class QSCarrierTest extends SysuiTestCase {
+
+    private QSCarrier mQSCarrier;
+    private TestableLooper mTestableLooper;
+
+    @Before
+    public void setUp() throws Exception {
+        mTestableLooper = TestableLooper.get(this);
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+        mTestableLooper.runWithLooper(() ->
+                mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
+    }
+
+    @Test
+    public void testUpdateState_first() {
+        CellSignalState c = new CellSignalState(true, 0, "", "", false);
+
+        assertTrue(mQSCarrier.updateState(c));
+    }
+
+    @Test
+    public void testUpdateState_same() {
+        CellSignalState c = new CellSignalState(true, 0, "", "", false);
+
+        assertTrue(mQSCarrier.updateState(c));
+        assertFalse(mQSCarrier.updateState(c));
+    }
+
+    @Test
+    public void testUpdateState_changed() {
+        CellSignalState c = new CellSignalState(true, 0, "", "", false);
+
+        assertTrue(mQSCarrier.updateState(c));
+
+        CellSignalState other = c.changeVisibility(false);
+
+        assertTrue(mQSCarrier.updateState(other));
+    }
+}
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 27b263f..138ea39 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_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
+import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
+import static android.provider.Settings.Secure.BUBBLE_IMPORTANT_CONVERSATIONS;
 import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 
@@ -55,6 +57,7 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -449,6 +452,7 @@
 
     @Test
     public void testBindNotification_bubbleActionVisibleWhenCanBubble()  {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1);
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mLauncherApps,
@@ -469,7 +473,8 @@
     }
 
     @Test
-    public void testBindNotification_bubbleActionVisibleWhenCannotBubble()  {
+    public void testBindNotification_bubbleAction_noBubbleMetadata()  {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1);
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mLauncherApps,
@@ -490,6 +495,28 @@
     }
 
     @Test
+    public void testBindNotification_bubbleActionGloballyOff()  {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mBubbleEntry,
+                null,
+                null,
+                null,
+                mIconFactory,
+                true);
+
+        View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+        assertEquals(View.GONE, bubbleView.getVisibility());
+    }
+
+    @Test
     public void testAddToHome() throws Exception {
         when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true);
 
@@ -550,6 +577,7 @@
 
     @Test
     public void testBubble_promotesBubble() throws Exception {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1);
         mNotificationChannel.setAllowBubbles(false);
         mConversationChannel.setAllowBubbles(false);
 
@@ -584,6 +612,7 @@
 
     @Test
     public void testBubble_demotesBubble() throws Exception {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1);
         mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
 
         mNotificationInfo.bindNotification(
@@ -617,6 +646,7 @@
 
     @Test
     public void testBubble_noChannelChange() throws Exception {
+        Settings.Global.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1);
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mLauncherApps,
@@ -645,7 +675,11 @@
     }
 
     @Test
-    public void testFavorite_favorite() throws Exception {
+    public void testFavorite_favorite_noBubble() throws Exception {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                BUBBLE_IMPORTANT_CONVERSATIONS, 0);
+        mNotificationChannel.setAllowBubbles(false);
+        mConversationChannel.setAllowBubbles(false);
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mLauncherApps,
@@ -673,6 +707,44 @@
         verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
                 anyString(), anyInt(), captor.capture());
         assertTrue(captor.getValue().isImportantConversation());
+        assertFalse(captor.getValue().canBubble());
+        verify(mBubbleController, never()).onUserCreatedBubbleFromNotification(mEntry);
+    }
+
+    @Test
+    public void testFavorite_favorite_bubble() throws Exception {
+        Settings.Secure.putInt(mContext.getContentResolver(),
+                BUBBLE_IMPORTANT_CONVERSATIONS, 1);
+        mNotificationChannel.setAllowBubbles(false);
+        mConversationChannel.setAllowBubbles(false);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mLauncherApps,
+                mMockPackageManager,
+                mMockINotificationManager,
+                mVisualStabilityManager,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                null,
+                null,
+                null,
+                mIconFactory,
+                true);
+
+        ImageButton fave = mNotificationInfo.findViewById(R.id.fave);
+        assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+                fave.getContentDescription().toString());
+
+        fave.performClick();
+        mTestableLooper.processAllMessages();
+
+        ArgumentCaptor<NotificationChannel> captor =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), anyInt(), captor.capture());
+        assertTrue(captor.getValue().isImportantConversation());
+        assertTrue(captor.getValue().canBubble());
     }
 
     @Test
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 7083281..f7eabac 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3430,16 +3430,16 @@
             // there is hope for it to become one if it validated, then it is needed.
             ensureRunningOnConnectivityServiceThread();
             if (nri.request.isRequest() && nai.satisfies(nri.request) &&
-                    (nai.isSatisfyingRequest(nri.request.requestId) ||
-                    // Note that this catches two important cases:
-                    // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
-                    //    is currently satisfying the request.  This is desirable when
-                    //    cellular ends up validating but WiFi does not.
-                    // 2. Unvalidated WiFi will not be reaped when validated cellular
-                    //    is currently satisfying the request.  This is desirable when
-                    //    WiFi ends up validating and out scoring cellular.
-                    nri.mSatisfier.getCurrentScore()
-                            < nai.getCurrentScoreAsValidated())) {
+                    (nai.isSatisfyingRequest(nri.request.requestId)
+                    // Note that canPossiblyBeat catches two important cases:
+                    // 1. Unvalidated slow networks will not be reaped when an unvalidated fast
+                    // network is currently satisfying the request. This is desirable for example
+                    // when cellular ends up validating but WiFi/Ethernet does not.
+                    // 2. Fast networks will not be reaped when a validated slow network is
+                    // currently satisfying the request. This is desirable for example when
+                    // Ethernet ends up validating and out scoring WiFi, or WiFi/Ethernet ends
+                    // up validating and out scoring cellular.
+                            || nai.canPossiblyBeat(nri.mSatisfier))) {
                 return false;
             }
         }
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 207a6aa..6852fbf 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -194,8 +194,6 @@
     // time
     private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100;
 
-    private static final String FEATURE_ID = "LocationService";
-
     private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
 
     private final Object mLock = new Object();
@@ -245,7 +243,7 @@
     private int mBatterySaverMode;
 
     private LocationManagerService(Context context) {
-        mContext = context.createFeatureContext(FEATURE_ID);
+        mContext = context;
         mHandler = FgThread.getHandler();
         mLocalService = new LocalService();
 
diff --git a/services/core/java/com/android/server/SensorNotificationService.java b/services/core/java/com/android/server/SensorNotificationService.java
index 9082dca..7f5befa 100644
--- a/services/core/java/com/android/server/SensorNotificationService.java
+++ b/services/core/java/com/android/server/SensorNotificationService.java
@@ -16,8 +16,10 @@
 
 package com.android.server;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.hardware.GeomagneticField;
 import android.hardware.Sensor;
 import android.hardware.SensorAdditionalInfo;
@@ -29,7 +31,9 @@
 import android.location.LocationManager;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Slog;
 
 public class SensorNotificationService extends SystemService
@@ -48,8 +52,6 @@
 
     private static final long MILLIS_2010_1_1 = 1262358000000l;
 
-    private static final String FEATURE_ID = "SensorNotificationService";
-
     private Context mContext;
     private SensorManager mSensorManager;
     private LocationManager mLocationManager;
@@ -59,8 +61,8 @@
     private long mLocalGeomagneticFieldUpdateTime = -LOCATION_MIN_TIME;
 
     public SensorNotificationService(Context context) {
-        super(context.createFeatureContext(FEATURE_ID));
-        mContext = getContext();
+        super(context);
+        mContext = context;
     }
 
     public void onStart() {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 53dbb93..3b0a1a3 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -4409,6 +4409,25 @@
             }
         }
 
+        @Override
+        public void prepareAppDataAfterInstall(String packageName, int uid) {
+            int userId = UserHandle.getUserId(uid);
+            final Environment.UserEnvironment userEnv = new Environment.UserEnvironment(userId);
+
+            // The installer may have downloaded OBBs for this newly installed application;
+            // make sure the OBB dir for the application is setup correctly, if it exists.
+            File[] packageObbDirs = userEnv.buildExternalStorageAppObbDirs(packageName);
+            for (File packageObbDir : packageObbDirs) {
+                try {
+                    mVold.fixupAppDir(packageObbDir.getCanonicalPath() + "/", uid);
+                } catch (IOException e) {
+                    Log.e(TAG, "Failed to get canonical path for " + packageName);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to fixup app dir for " + packageName);
+                }
+            }
+        }
+
         public boolean hasExternalStorage(int uid, String packageName) {
             // No need to check for system uid. This avoids a deadlock between
             // PackageManagerService and AppOpsService.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 430a5b9..50f43b5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -49,6 +49,7 @@
 import android.app.PendingIntent;
 import android.app.Service;
 import android.app.ServiceStartArgs;
+import android.app.admin.DevicePolicyEventLogger;
 import android.appwidget.AppWidgetManagerInternal;
 import android.content.ComponentName;
 import android.content.ComponentName.WithComponentName;
@@ -79,6 +80,7 @@
 import android.os.TransactionTooLargeException;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.stats.devicepolicy.DevicePolicyEnums;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -1912,6 +1914,8 @@
                 requestServiceBindingLocked(s, b.intent, callerFg, false);
             }
 
+            maybeLogBindCrossProfileService(userId, callingPackage, callerApp.info.uid);
+
             getServiceMapLocked(s.userId).ensureNotStartingBackgroundLocked(s);
 
         } finally {
@@ -1921,6 +1925,21 @@
         return 1;
     }
 
+    private void maybeLogBindCrossProfileService(
+            int userId, String callingPackage, int callingUid) {
+        if (UserHandle.isCore(callingUid)) {
+            return;
+        }
+        final int callingUserId = UserHandle.getCallingUserId();
+        if (callingUserId == userId
+                || !mAm.mUserController.isSameProfileGroup(callingUserId, userId)) {
+            return;
+        }
+        DevicePolicyEventLogger.createEvent(DevicePolicyEnums.BIND_CROSS_PROFILE_SERVICE)
+                .setStrings(callingPackage)
+                .write();
+    }
+
     void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
         final long origId = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 4612cfd..3860904 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,6 +16,8 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.transportNamesOf;
 
 import android.annotation.NonNull;
@@ -475,24 +477,16 @@
         return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
     }
 
-    private int getCurrentScore(boolean pretendValidated) {
-        // TODO: We may want to refactor this into a NetworkScore class that takes a base score from
-        // the NetworkAgent and signals from the NetworkAgent and uses those signals to modify the
-        // score.  The NetworkScore class would provide a nice place to centralize score constants
-        // so they are not scattered about the transports.
-
+    /** Gets the current score */
+    public int getCurrentScore() {
         // If this network is explicitly selected and the user has decided to use it even if it's
-        // unvalidated, give it the maximum score. Also give it the maximum score if it's explicitly
-        // selected and we're trying to see what its score could be. This ensures that we don't tear
-        // down an explicitly selected network before the user gets a chance to prefer it when
-        // a higher-scoring network (e.g., Ethernet) is available.
-        if (networkAgentConfig.explicitlySelected
-                && (networkAgentConfig.acceptUnvalidated || pretendValidated)) {
+        // unvalidated, give it the maximum score.
+        if (networkAgentConfig.explicitlySelected && networkAgentConfig.acceptUnvalidated) {
             return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
         }
 
         int score = mNetworkScore.getLegacyScore();
-        if (!lastValidated && !pretendValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) {
+        if (!lastValidated && !ignoreWifiUnvalidationPenalty() && !isVPN()) {
             score -= ConnectivityConstants.UNVALIDATED_SCORE_PENALTY;
         }
         if (score < 0) score = 0;
@@ -508,18 +502,6 @@
         return isWifi && !avoidBadWifi && everValidated;
     }
 
-    // Get the current score for this Network.  This may be modified from what the
-    // NetworkAgent sent, as it has modifiers applied to it.
-    public int getCurrentScore() {
-        return getCurrentScore(false);
-    }
-
-    // Get the current score for this Network as if it was validated.  This may be modified from
-    // what the NetworkAgent sent, as it has modifiers applied to it.
-    public int getCurrentScoreAsValidated() {
-        return getCurrentScore(true);
-    }
-
     public void setNetworkScore(@NonNull NetworkScore ns) {
         mNetworkScore = ns;
     }
@@ -629,6 +611,41 @@
         mLingering = false;
     }
 
+    /**
+     * Returns whether this NAI has any chance of ever beating this other agent.
+     *
+     * The chief use case of this is the decision to tear down this network. ConnectivityService
+     * tears down networks that don't satisfy any request, unless they have a chance to beat any
+     * existing satisfier.
+     *
+     * @param other the agent to beat
+     * @return whether this should be given more time to try and beat the other agent
+     * TODO : remove this and migrate to a ranker-based approach
+     */
+    public boolean canPossiblyBeat(@NonNull final NetworkAgentInfo other) {
+        // Any explicitly selected network should be held on.
+        if (networkAgentConfig.explicitlySelected) return true;
+        // An outscored exiting network should be torn down.
+        if (mNetworkScore.isExiting()) return false;
+        // If this network is validated it can be torn down as it can't hope to be better than
+        // it already is.
+        if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) return false;
+        // If neither network is validated, keep both until at least one does.
+        if (!other.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) return true;
+        // If this network is not metered but the other is, it should be preferable if it validates.
+        if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
+                && !other.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+            return true;
+        }
+
+        // If the control comes here :
+        // • This network is neither exiting or explicitly selected
+        // • This network is not validated, but the other is
+        // • This network is metered, or both networks are unmetered
+        // Keep it if it's expected to be faster than the other., should it validate.
+        return mNetworkScore.probablyFasterThan(other.mNetworkScore);
+    }
+
     public void dumpLingerTimers(PrintWriter pw) {
         for (LingerTimer timer : mLingerTimers) { pw.println(timer); }
     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkRanker.java b/services/core/java/com/android/server/connectivity/NetworkRanker.java
index c536ab2..80d46e0 100644
--- a/services/core/java/com/android/server/connectivity/NetworkRanker.java
+++ b/services/core/java/com/android/server/connectivity/NetworkRanker.java
@@ -16,6 +16,9 @@
 
 package com.android.server.connectivity;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkScore.POLICY_IGNORE_ON_WIFI;
 
 import static com.android.internal.util.FunctionalUtils.findFirst;
@@ -42,13 +45,20 @@
             @NonNull final Collection<NetworkAgentInfo> nais) {
         final ArrayList<NetworkAgentInfo> candidates = new ArrayList<>(nais);
         candidates.removeIf(nai -> !nai.satisfies(request));
-        // Enforce policy.
-        filterBadWifiAvoidancePolicy(candidates);
+
+        // Enforce policy. The order in which the policy is computed is essential, because each
+        // step may remove some of the candidates. For example, filterValidated drops non-validated
+        // networks in presence of validated networks for INTERNET requests, but the bad wifi
+        // avoidance policy takes priority over this, so it must be done before.
+        filterVpn(candidates);
+        filterExplicitlySelected(candidates);
+        filterBadWifiAvoidance(candidates);
+        filterValidated(request, candidates);
 
         NetworkAgentInfo bestNetwork = null;
         int bestScore = Integer.MIN_VALUE;
         for (final NetworkAgentInfo nai : candidates) {
-            final int score = nai.getCurrentScore();
+            final int score = nai.getNetworkScore().getLegacyScore();
             if (score > bestScore) {
                 bestNetwork = nai;
                 bestScore = score;
@@ -57,9 +67,27 @@
         return bestNetwork;
     }
 
-    // If some network with wifi transport is present, drop all networks with POLICY_IGNORE_ON_WIFI.
-    private void filterBadWifiAvoidancePolicy(
+    // If a network is a VPN it has priority.
+    private void filterVpn(@NonNull final ArrayList<NetworkAgentInfo> candidates) {
+        final NetworkAgentInfo vpn = findFirst(candidates,
+                nai -> nai.networkCapabilities.hasTransport(TRANSPORT_VPN));
+        if (null == vpn) return; // No VPN : this policy doesn't apply.
+        candidates.removeIf(nai -> !nai.networkCapabilities.hasTransport(TRANSPORT_VPN));
+    }
+
+    // If some network is explicitly selected and set to accept unvalidated connectivity, then
+    // drop all networks that are not explicitly selected.
+    private void filterExplicitlySelected(
             @NonNull final ArrayList<NetworkAgentInfo> candidates) {
+        final NetworkAgentInfo explicitlySelected = findFirst(candidates,
+                nai -> nai.networkAgentConfig.explicitlySelected
+                        && nai.networkAgentConfig.acceptUnvalidated);
+        if (null == explicitlySelected) return; // No explicitly selected network accepting unvalid
+        candidates.removeIf(nai -> !nai.networkAgentConfig.explicitlySelected);
+    }
+
+    // If some network with wifi transport is present, drop all networks with POLICY_IGNORE_ON_WIFI.
+    private void filterBadWifiAvoidance(@NonNull final ArrayList<NetworkAgentInfo> candidates) {
         final NetworkAgentInfo wifi = findFirst(candidates,
                 nai -> nai.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
                         && nai.everValidated
@@ -71,4 +99,16 @@
         if (null == wifi) return; // No wifi : this policy doesn't apply
         candidates.removeIf(nai -> nai.getNetworkScore().hasPolicy(POLICY_IGNORE_ON_WIFI));
     }
+
+    // If some network is validated and the request asks for INTERNET, drop all networks that are
+    // not validated.
+    private void filterValidated(@NonNull final NetworkRequest request,
+            @NonNull final ArrayList<NetworkAgentInfo> candidates) {
+        if (!request.hasCapability(NET_CAPABILITY_INTERNET)) return;
+        final NetworkAgentInfo validated = findFirst(candidates,
+                nai -> nai.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
+        if (null == validated) return; // No validated network
+        candidates.removeIf(nai ->
+                !nai.networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED));
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index ac41434..18adc0b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.hardware.display.DeviceProductInfo;
 import android.hardware.display.DisplayViewport;
 import android.util.DisplayMetrics;
 import android.view.Display;
@@ -288,6 +289,13 @@
     public DisplayAddress address;
 
     /**
+     * Product-specific information about the display or the directly connected device on the
+     * display chain. For example, if the display is transitively connected, this field may contain
+     * product information about the intermediate device.
+     */
+    public DeviceProductInfo deviceProductInfo;
+
+    /**
      * Display state.
      */
     public int state = Display.STATE_ON;
@@ -360,6 +368,7 @@
                 || rotation != other.rotation
                 || type != other.type
                 || !Objects.equals(address, other.address)
+                || !Objects.equals(deviceProductInfo, other.deviceProductInfo)
                 || ownerUid != other.ownerUid
                 || !Objects.equals(ownerPackageName, other.ownerPackageName)) {
             diff |= DIFF_OTHER;
@@ -396,6 +405,7 @@
         rotation = other.rotation;
         type = other.type;
         address = other.address;
+        deviceProductInfo = other.deviceProductInfo;
         state = other.state;
         ownerUid = other.ownerUid;
         ownerPackageName = other.ownerPackageName;
@@ -429,6 +439,7 @@
         if (address != null) {
             sb.append(", address ").append(address);
         }
+        sb.append(", deviceProductInfo ").append(deviceProductInfo);
         sb.append(", state ").append(Display.stateToString(state));
         if (ownerUid != 0 || ownerPackageName != null) {
             sb.append(", owner ").append(ownerPackageName);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 4ebbdda..e578ac1 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -513,6 +513,7 @@
                 mInfo.densityDpi = (int) (mDisplayInfo.density * 160 + 0.5f);
                 mInfo.xDpi = config.xDpi;
                 mInfo.yDpi = config.yDpi;
+                mInfo.deviceProductInfo = mDisplayInfo.deviceProductInfo;
 
                 // Assume that all built-in displays that have secure output (eg. HDCP) also
                 // support compositing from gralloc protected buffers.
@@ -891,8 +892,8 @@
             pw.println("mBacklight=" + mBacklight);
             pw.println("mAllmSupported=" + mAllmSupported);
             pw.println("mAllmRequested=" + mAllmRequested);
-            pw.println("mGameContentTypeSupported" + mGameContentTypeSupported);
-            pw.println("mGameContentTypeRequested" + mGameContentTypeRequested);
+            pw.println("mGameContentTypeSupported=" + mGameContentTypeSupported);
+            pw.println("mGameContentTypeRequested=" + mGameContentTypeRequested);
             pw.println("mDisplayInfo=" + mDisplayInfo);
             pw.println("mDisplayConfigs=");
             for (int i = 0; i < mDisplayConfigs.length; i++) {
@@ -902,14 +903,7 @@
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
             }
-            pw.print("mSupportedColorModes=[");
-            for (int i = 0; i < mSupportedColorModes.size(); i++) {
-                if (i != 0) {
-                    pw.print(", ");
-                }
-                pw.print(mSupportedColorModes.get(i));
-            }
-            pw.println("]");
+            pw.print("mSupportedColorModes=" + mSupportedColorModes.toString());
         }
 
         private int findDisplayConfigIdLocked(int modeId) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 0c9445a..ac81a6c 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -269,6 +269,7 @@
 
             mBaseDisplayInfo.type = deviceInfo.type;
             mBaseDisplayInfo.address = deviceInfo.address;
+            mBaseDisplayInfo.deviceProductInfo = deviceInfo.deviceProductInfo;
             mBaseDisplayInfo.name = deviceInfo.name;
             mBaseDisplayInfo.uniqueId = deviceInfo.uniqueId;
             mBaseDisplayInfo.appWidth = maskedWidth;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 6174e54..b84d322 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -77,7 +77,7 @@
 
     private static final int NUM_LOGICAL_ADDRESS = 16;
 
-    private static final int MAX_CEC_MESSAGE_HISTORY = 200;
+    private static final int MAX_HDMI_MESSAGE_HISTORY = 250;
 
     // Predicate for whether the given logical address is remote device's one or not.
     private final Predicate<Integer> mRemoteDeviceAddressPredicate = new Predicate<Integer>() {
@@ -111,9 +111,9 @@
     // Stores the local CEC devices in the system. Device type is used for key.
     private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
 
-    // Stores recent CEC messages history for debugging purpose.
-    private final ArrayBlockingQueue<MessageHistoryRecord> mMessageHistory =
-            new ArrayBlockingQueue<>(MAX_CEC_MESSAGE_HISTORY);
+    // Stores recent CEC messages and HDMI Hotplug event history for debugging purpose.
+    private final ArrayBlockingQueue<Dumpable> mMessageHistory =
+            new ArrayBlockingQueue<>(MAX_HDMI_MESSAGE_HISTORY);
 
     private final NativeWrapper mNativeWrapperImpl;
 
@@ -618,7 +618,7 @@
     void sendCommand(final HdmiCecMessage cecMessage,
             final HdmiControlService.SendMessageCallback callback) {
         assertRunOnServiceThread();
-        addMessageToHistory(false /* isReceived */, cecMessage);
+        addCecMessageToHistory(false /* isReceived */, cecMessage);
         runOnIoThread(new Runnable() {
             @Override
             public void run() {
@@ -658,7 +658,7 @@
         assertRunOnServiceThread();
         HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);
         HdmiLogger.debug("[R]:" + command);
-        addMessageToHistory(true /* isReceived */, command);
+        addCecMessageToHistory(true /* isReceived */, command);
         onReceiveCommand(command);
     }
 
@@ -669,16 +669,26 @@
     private void handleHotplug(int port, boolean connected) {
         assertRunOnServiceThread();
         HdmiLogger.debug("Hotplug event:[port:%d, connected:%b]", port, connected);
+        addHotplugEventToHistory(port, connected);
         mService.onHotplug(port, connected);
     }
 
     @ServiceThreadOnly
-    private void addMessageToHistory(boolean isReceived, HdmiCecMessage message) {
+    private void addHotplugEventToHistory(int port, boolean connected) {
         assertRunOnServiceThread();
-        MessageHistoryRecord record = new MessageHistoryRecord(isReceived, message);
-        if (!mMessageHistory.offer(record)) {
+        addEventToHistory(new HotplugHistoryRecord(port, connected));
+    }
+
+    @ServiceThreadOnly
+    private void addCecMessageToHistory(boolean isReceived, HdmiCecMessage message) {
+        assertRunOnServiceThread();
+        addEventToHistory(new MessageHistoryRecord(isReceived, message));
+    }
+
+    private void addEventToHistory(Dumpable event) {
+        if (!mMessageHistory.offer(event)) {
             mMessageHistory.poll();
-            mMessageHistory.offer(record);
+            mMessageHistory.offer(event);
         }
     }
 
@@ -689,10 +699,11 @@
             mLocalDevices.valueAt(i).dump(pw);
             pw.decreaseIndent();
         }
+
         pw.println("CEC message history:");
         pw.increaseIndent();
         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-        for (MessageHistoryRecord record : mMessageHistory) {
+        for (Dumpable record : mMessageHistory) {
             record.dump(pw, sdf);
         }
         pw.decreaseIndent();
@@ -792,17 +803,27 @@
         }
     }
 
-    private final class MessageHistoryRecord {
-        private final long mTime;
+    private abstract static class Dumpable {
+        protected final long mTime;
+
+        Dumpable() {
+            mTime = System.currentTimeMillis();
+        }
+
+        abstract void dump(IndentingPrintWriter pw, SimpleDateFormat sdf);
+    }
+
+    private static final class MessageHistoryRecord extends Dumpable {
         private final boolean mIsReceived; // true if received message and false if sent message
         private final HdmiCecMessage mMessage;
 
-        public MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) {
-            mTime = System.currentTimeMillis();
+        MessageHistoryRecord(boolean isReceived, HdmiCecMessage message) {
+            super();
             mIsReceived = isReceived;
             mMessage = message;
         }
 
+        @Override
         void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
             pw.print(mIsReceived ? "[R]" : "[S]");
             pw.print(" time=");
@@ -811,4 +832,26 @@
             pw.println(mMessage);
         }
     }
+
+    private static final class HotplugHistoryRecord extends Dumpable {
+        private final int mPort;
+        private final boolean mConnected;
+
+        HotplugHistoryRecord(int port, boolean connected) {
+            super();
+            mPort = port;
+            mConnected = connected;
+        }
+
+        @Override
+        void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) {
+            pw.print("[H]");
+            pw.print(" time=");
+            pw.print(sdf.format(new Date(mTime)));
+            pw.print(" hotplug port=");
+            pw.print(mPort);
+            pw.print(" connected=");
+            pw.println(mConnected);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/location/CountryDetectorBase.java b/services/core/java/com/android/server/location/CountryDetectorBase.java
index b158388..8326ef9 100644
--- a/services/core/java/com/android/server/location/CountryDetectorBase.java
+++ b/services/core/java/com/android/server/location/CountryDetectorBase.java
@@ -31,15 +31,13 @@
  * @hide
  */
 public abstract class CountryDetectorBase {
-    private static final String FEATURE_ID = "CountryDetector";
-
     protected final Handler mHandler;
     protected final Context mContext;
     protected CountryListener mListener;
     protected Country mDetectedCountry;
 
-    public CountryDetectorBase(Context context) {
-        mContext = context.createFeatureContext(FEATURE_ID);
+    public CountryDetectorBase(Context ctx) {
+        mContext = ctx;
         mHandler = new Handler();
     }
 
@@ -47,7 +45,7 @@
      * Start detecting the country that the user is in.
      *
      * @return the country if it is available immediately, otherwise null should
-     * be returned.
+     *         be returned.
      */
     public abstract Country detectCountry();
 
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 05867ba..f7e1398 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -214,6 +214,8 @@
      * Updates the mSessionInfo. Returns true if the session info is changed.
      */
     boolean updateSessionInfosIfNeededLocked() {
+        // Prevent to execute this method before mBtRouteProvider is created.
+        if (mBtRouteProvider == null) return false;
         RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(0);
 
         RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index fd86f1d..0d402e5 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3440,6 +3440,27 @@
         }
 
         @Override
+        public ParceledListSlice<ConversationChannelWrapper> getConversations(
+                boolean onlyImportant) {
+            enforceSystemOrSystemUI("getConversations");
+            ArrayList<ConversationChannelWrapper> conversations =
+                    mPreferencesHelper.getConversations(onlyImportant);
+            for (ConversationChannelWrapper conversation : conversations) {
+                LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+                        .setPackage(conversation.getPkg())
+                        .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+                        .setShortcutIds(Arrays.asList(
+                                conversation.getNotificationChannel().getConversationId()));
+                List<ShortcutInfo> shortcuts = mLauncherAppsService.getShortcuts(
+                        query, UserHandle.of(UserHandle.getUserId(conversation.getUid())));
+                if (shortcuts != null && !shortcuts.isEmpty()) {
+                    conversation.setShortcutInfo(shortcuts.get(0));
+                }
+            }
+            return new ParceledListSlice<>(conversations);
+        }
+
+        @Override
         public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
                 String pkg, int uid, boolean includeDeleted) {
             enforceSystemOrSystemUI("getNotificationChannelGroupsForPackage");
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 20c8625..b8186ed 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -1186,6 +1186,44 @@
         return groups;
     }
 
+    public ArrayList<ConversationChannelWrapper> getConversations(boolean onlyImportant) {
+        synchronized (mPackagePreferences) {
+            ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>();
+
+            for (PackagePreferences p : mPackagePreferences.values()) {
+                int N = p.channels.size();
+                for (int i = 0; i < N; i++) {
+                    final NotificationChannel nc = p.channels.valueAt(i);
+                    if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()
+                            && (nc.isImportantConversation() || !onlyImportant)) {
+                        ConversationChannelWrapper conversation = new ConversationChannelWrapper();
+                        conversation.setPkg(p.pkg);
+                        conversation.setUid(p.uid);
+                        conversation.setNotificationChannel(nc);
+                        conversation.setParentChannelLabel(
+                                p.channels.get(nc.getParentChannelId()).getName());
+                        boolean blockedByGroup = false;
+                        if (nc.getGroup() != null) {
+                            NotificationChannelGroup group = p.groups.get(nc.getGroup());
+                            if (group != null) {
+                                if (group.isBlocked()) {
+                                    blockedByGroup = true;
+                                } else {
+                                    conversation.setGroupLabel(group.getName());
+                                }
+                            }
+                        }
+                        if (!blockedByGroup) {
+                            conversations.add(conversation);
+                        }
+                    }
+                }
+            }
+
+            return conversations;
+        }
+    }
+
     public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) {
         Objects.requireNonNull(pkg);
         synchronized (mPackagePreferences) {
@@ -1199,6 +1237,8 @@
                 final NotificationChannel nc = r.channels.valueAt(i);
                 if (!TextUtils.isEmpty(nc.getConversationId()) && !nc.isDeleted()) {
                     ConversationChannelWrapper conversation = new ConversationChannelWrapper();
+                    conversation.setPkg(r.pkg);
+                    conversation.setUid(r.uid);
                     conversation.setNotificationChannel(nc);
                     conversation.setParentChannelLabel(
                             r.channels.get(nc.getParentChannelId()).getName());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f6eb76b..2a3f7ed 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -15158,10 +15158,13 @@
 
                 // We purposefully exclude FLAG_STORAGE_EXTERNAL here, since
                 // this task was only focused on moving data on internal storage.
+                // We don't want ART profiles cleared, because they don't move,
+                // so we would be deleting the only copy (b/149200535).
+                final int flags = FLAG_STORAGE_DE | FLAG_STORAGE_CE
+                        | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES;
                 for (int userId : userIds) {
                     try {
-                        mInstaller.destroyAppData(volumeUuid, move.packageName, userId,
-                                StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE, 0);
+                        mInstaller.destroyAppData(volumeUuid, move.packageName, userId, flags, 0);
                     } catch (InstallerException e) {
                         Slog.w(TAG, String.valueOf(e));
                     }
@@ -21733,6 +21736,7 @@
         }
 
         UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+        StorageManagerInternal smInternal = mInjector.getStorageManagerInternal();
         for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
             final int flags;
             if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
@@ -21746,6 +21750,13 @@
             if (ps.getInstalled(user.id)) {
                 // TODO: when user data is locked, mark that we're still dirty
                 prepareAppDataLIF(pkg, user.id, flags);
+
+                if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+                    // Prepare app data on external storage; currently this is used to
+                    // setup any OBB dirs that were created by the installer correctly.
+                    int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
+                    smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index 761fbf8..e72ba8d 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -50,7 +50,6 @@
         implements AlarmManager.OnAlarmListener, Handler.Callback, LocationListener {
 
     private static final String TAG = "TwilightService";
-    private static final String FEATURE_ID = "TwilightService";
     private static final boolean DEBUG = false;
 
     private static final int MSG_START_LISTENING = 1;
@@ -74,7 +73,7 @@
     protected TwilightState mLastTwilightState;
 
     public TwilightService(Context context) {
-        super(context.createFeatureContext(FEATURE_ID));
+        super(context);
         mHandler = new Handler(Looper.getMainLooper(), this);
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 0adf15c..7f9732b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -108,6 +108,7 @@
     private static final int UID_N_MR1 = 0;
     private static final UserHandle USER = UserHandle.of(0);
     private static final int UID_O = 1111;
+    private static final int UID_P = 2222;
     private static final String SYSTEM_PKG = "android";
     private static final int SYSTEM_UID = 1000;
     private static final UserHandle USER2 = UserHandle.of(10);
@@ -141,9 +142,11 @@
         upgrade.targetSdkVersion = Build.VERSION_CODES.O;
         when(mPm.getApplicationInfoAsUser(eq(PKG_N_MR1), anyInt(), anyInt())).thenReturn(legacy);
         when(mPm.getApplicationInfoAsUser(eq(PKG_O), anyInt(), anyInt())).thenReturn(upgrade);
+        when(mPm.getApplicationInfoAsUser(eq(PKG_P), anyInt(), anyInt())).thenReturn(upgrade);
         when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade);
         when(mPm.getPackageUidAsUser(eq(PKG_N_MR1), anyInt())).thenReturn(UID_N_MR1);
         when(mPm.getPackageUidAsUser(eq(PKG_O), anyInt())).thenReturn(UID_O);
+        when(mPm.getPackageUidAsUser(eq(PKG_P), anyInt())).thenReturn(UID_P);
         when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID);
         PackageInfo info = mock(PackageInfo.class);
         info.signatures = new Signature[] {mock(Signature.class)};
@@ -2945,6 +2948,92 @@
     }
 
     @Test
+    public void testGetConversations_all() {
+        String convoId = "convo";
+        NotificationChannel messages =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
+        NotificationChannel calls =
+                new NotificationChannel("calls", "Calls", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, calls, true, false);
+        NotificationChannel p =
+                new NotificationChannel("p calls", "Calls", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, UID_P, p, true, false);
+
+        NotificationChannel channel =
+                new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
+        channel.setConversationId(messages.getId(), convoId);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+        NotificationChannel diffConvo =
+                new NotificationChannel("B person msgs", "messages from B", IMPORTANCE_DEFAULT);
+        diffConvo.setConversationId(p.getId(), "different convo");
+        mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, true, false);
+
+        NotificationChannel channel2 =
+                new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
+        channel2.setConversationId(calls.getId(), convoId);
+        channel2.setImportantConversation(true);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+
+        List<ConversationChannelWrapper> convos = mHelper.getConversations(false);
+
+        assertEquals(3, convos.size());
+        assertTrue(conversationWrapperContainsChannel(convos, channel));
+        assertTrue(conversationWrapperContainsChannel(convos, diffConvo));
+        assertTrue(conversationWrapperContainsChannel(convos, channel2));
+    }
+
+    @Test
+    public void testGetConversations_onlyImportant() {
+        String convoId = "convo";
+        NotificationChannel messages =
+                new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
+        NotificationChannel calls =
+                new NotificationChannel("calls", "Calls", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_O, UID_O, calls, true, false);
+        NotificationChannel p =
+                new NotificationChannel("p calls", "Calls", IMPORTANCE_DEFAULT);
+        mHelper.createNotificationChannel(PKG_P, UID_P, p, true, false);
+
+        NotificationChannel channel =
+                new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
+        channel.setConversationId(messages.getId(), convoId);
+        channel.setImportantConversation(true);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
+
+        NotificationChannel diffConvo =
+                new NotificationChannel("B person msgs", "messages from B", IMPORTANCE_DEFAULT);
+        diffConvo.setConversationId(p.getId(), "different convo");
+        diffConvo.setImportantConversation(true);
+        mHelper.createNotificationChannel(PKG_P, UID_P, diffConvo, true, false);
+
+        NotificationChannel channel2 =
+                new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
+        channel2.setConversationId(calls.getId(), convoId);
+        mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
+
+        List<ConversationChannelWrapper> convos = mHelper.getConversations(true);
+
+        assertEquals(2, convos.size());
+        assertTrue(conversationWrapperContainsChannel(convos, channel));
+        assertTrue(conversationWrapperContainsChannel(convos, diffConvo));
+        assertFalse(conversationWrapperContainsChannel(convos, channel2));
+    }
+
+    private boolean conversationWrapperContainsChannel(List<ConversationChannelWrapper> list,
+            NotificationChannel expected) {
+        for (ConversationChannelWrapper ccw : list) {
+            if (ccw.getNotificationChannel().equals(expected)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Test
     public void testGetConversations_invalidPkg() {
         assertThat(mHelper.getConversations("bad", 1)).isEmpty();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index b5663bd..851b052 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -260,4 +260,9 @@
     public SurfaceControl.Transaction unsetColor(SurfaceControl sc) {
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction setShadowRadius(SurfaceControl sc, float shadowRadius) {
+        return this;
+    }
 }
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 0886975..220cdce 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -2054,14 +2054,15 @@
         assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
-        // Bring up wifi with a score of 70.
+        // Bring up validated wifi.
         // Cell is lingered because it would not satisfy any request, even if it validated.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
-        mWiFiNetworkAgent.adjustScore(50);
-        mWiFiNetworkAgent.connect(false);   // Score: 70
+        mWiFiNetworkAgent.connect(true);   // Score: 60
         callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        // TODO: Investigate sending validated before losing.
         callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
-        defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
+        defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork());
 
@@ -5845,7 +5846,7 @@
 
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
         mWiFiNetworkAgent.connect(true);
-        trustedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
+        trustedCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
         verify(mNetworkManagementService).setDefaultNetId(eq(mWiFiNetworkAgent.getNetwork().netId));
         reset(mNetworkManagementService);
 
diff --git a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
index a6b371a..d2532c2 100644
--- a/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
+++ b/tests/net/java/com/android/server/connectivity/NetworkRankerTest.kt
@@ -16,14 +16,18 @@
 
 package com.android.server.connectivity
 
+import android.net.ConnectivityManager.TYPE_WIFI
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkAgentConfig
 import android.net.NetworkCapabilities
+import android.net.NetworkInfo
 import android.net.NetworkRequest
+import android.net.NetworkScore
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 import kotlin.test.assertEquals
 import kotlin.test.assertNull
@@ -33,10 +37,24 @@
 class NetworkRankerTest {
     private val ranker = NetworkRanker()
 
-    private fun makeNai(satisfy: Boolean, score: Int) = mock(NetworkAgentInfo::class.java).also {
-        doReturn(satisfy).`when`(it).satisfies(any())
-        doReturn(score).`when`(it).currentScore
-        it.networkCapabilities = NetworkCapabilities()
+    private fun makeNai(satisfy: Boolean, score: Int) = object : NetworkAgentInfo(
+            null /* messenger */,
+            null /* asyncChannel*/,
+            Network(100),
+            NetworkInfo(TYPE_WIFI, 0 /* subtype */, "" /* typename */, "" /* subtypename */),
+            LinkProperties(),
+            NetworkCapabilities(),
+            NetworkScore.Builder().setLegacyScore(score).build(),
+            null /* context */,
+            null /* handler */,
+            NetworkAgentConfig(),
+            null /* connectivityService */,
+            null /* netd */,
+            null /* dnsResolver */,
+            null /* networkManagementService */,
+            0 /* factorySerialNumber */) {
+        override fun satisfies(request: NetworkRequest?): Boolean = satisfy
+        override fun getCurrentScore(): Int = score
     }
 
     @Test