Merge "Improved dumpsys device_policy:"
diff --git a/Android.bp b/Android.bp
index 6b55cc9..c90e00c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -580,6 +580,7 @@
     aidl: {
         generate_get_transaction_name: true,
         local_include_dirs: ["media/aidl"],
+        include_dirs: ["frameworks/av/aidl"],
     },
     dxflags: [
         "--core-library",
@@ -596,6 +597,7 @@
         "framework-platform-compat-config",
         // TODO: remove gps_debug and protolog.conf.json when the build system propagates "required" properly.
         "gps_debug.conf",
+        "icu4j-platform-compat-config",
         "libcore-platform-compat-config",
         "protolog.conf.json.gz",
         "services-platform-compat-config",
@@ -614,6 +616,7 @@
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
         // in favor of an API stubs dependency in java_library "framework" below.
         "mimemap",
+        "av-types-aidl-java",
         "mediatranscoding_aidl_interface-java",
         "soundtrigger_middleware-aidl-java",
     ],
diff --git a/ApiDocs.bp b/ApiDocs.bp
index faa0e5d..7ed7ec5 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -86,6 +86,7 @@
     // TODO(b/169090544): remove below aidl includes.
     aidl: {
         local_include_dirs: ["media/aidl"],
+        include_dirs: ["frameworks/av/aidl"],
     },
 }
 
@@ -157,6 +158,7 @@
     // TODO(b/169090544): remove below aidl includes.
     aidl: {
         local_include_dirs: ["media/aidl"],
+        include_dirs: ["frameworks/av/aidl"],
     },
 }
 
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 8090bec..228b3da 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -57,6 +57,7 @@
             "telephony/java",
             "media/aidl",
         ],
+        include_dirs: ["frameworks/av/aidl"],
     },
     // These are libs from framework-internal-utils that are required (i.e. being referenced)
     // from framework-non-updatable-sources. Add more here when there's a need.
@@ -338,19 +339,6 @@
         "framework-wifi.stubs",
         "private-stub-annotations-jar",
     ],
-    defaults: ["android_defaults_stubs_current"],
-}
-
-java_library_static {
-    name: "android_stubs_current",
-    static_libs: ["android_merged_stubs_current"],
-    defaults: ["android_defaults_stubs_current"],
-}
-
-java_library_static {
-    name: "android_system_monolith_stubs_current",
-    srcs: [ ":system-api-stubs-docs" ],
-    static_libs: [ "private-stub-annotations-jar" ],
     defaults: [
         "android_defaults_stubs_current",
         "android_stubs_dists_default",
@@ -369,6 +357,21 @@
 }
 
 java_library_static {
+    name: "android_stubs_current",
+    static_libs: ["android_merged_stubs_current"],
+    defaults: ["android_defaults_stubs_current"],
+}
+
+java_library_static {
+    name: "android_system_monolith_stubs_current",
+    srcs: [ ":system-api-stubs-docs" ],
+    static_libs: [ "private-stub-annotations-jar" ],
+    defaults: [
+        "android_defaults_stubs_current",
+    ],
+}
+
+java_library_static {
     name: "android_system_merged_stubs_current",
     srcs: [ ":system-api-stubs-docs-non-updatable" ],
     static_libs: [
diff --git a/apex/Android.bp b/apex/Android.bp
index c5b4901..0a535a8 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -112,6 +112,8 @@
     ],
     stubs_source_visibility: ["//visibility:private"],
 
+    defaults_visibility: ["//visibility:private"],
+
     // Collates API usages from each module for further analysis.
     plugins: ["java_api_finder"],
 
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index c8a04d6..ba2a8a3 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -268,6 +268,7 @@
      */
     Bundle mIdleOptions;
 
+    // TODO(b/172085676): Move inside alarm store.
     private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
             new SparseArray<>();
     private final SparseArray<AlarmManager.AlarmClockInfo> mTmpSparseAlarmClockArray =
@@ -276,6 +277,9 @@
             new SparseBooleanArray();
     private boolean mNextAlarmClockMayChange;
 
+    @GuardedBy("mLock")
+    private final Runnable mAlarmClockUpdater = () -> mNextAlarmClockMayChange = true;
+
     // May only use on mHandler's thread, locking not required.
     private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
             new SparseArray<>();
@@ -410,6 +414,9 @@
         private static final String KEY_APP_STANDBY_RESTRICTED_WINDOW =
                 "app_standby_restricted_window";
 
+        @VisibleForTesting
+        static final String KEY_LAZY_BATCHING = "lazy_batching";
+
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
         private static final long DEFAULT_MAX_INTERVAL = 365 * DateUtils.DAY_IN_MILLIS;
@@ -432,6 +439,8 @@
         private static final int DEFAULT_APP_STANDBY_RESTRICTED_QUOTA = 1;
         private static final long DEFAULT_APP_STANDBY_RESTRICTED_WINDOW = MILLIS_IN_DAY;
 
+        private static final boolean DEFAULT_LAZY_BATCHING = false;
+
         // Minimum futurity of a new alarm
         public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
 
@@ -460,6 +469,8 @@
         public int APP_STANDBY_RESTRICTED_QUOTA = DEFAULT_APP_STANDBY_RESTRICTED_QUOTA;
         public long APP_STANDBY_RESTRICTED_WINDOW = DEFAULT_APP_STANDBY_RESTRICTED_WINDOW;
 
+        public boolean LAZY_BATCHING = DEFAULT_LAZY_BATCHING;
+
         private long mLastAllowWhileIdleWhitelistDuration = -1;
 
         Constants() {
@@ -538,6 +549,14 @@
                         case KEY_APP_STANDBY_RESTRICTED_WINDOW:
                             updateStandbyWindowsLocked();
                             break;
+                        case KEY_LAZY_BATCHING:
+                            final boolean oldLazyBatching = LAZY_BATCHING;
+                            LAZY_BATCHING = properties.getBoolean(
+                                    KEY_LAZY_BATCHING, DEFAULT_LAZY_BATCHING);
+                            if (oldLazyBatching != LAZY_BATCHING) {
+                                migrateAlarmsToNewStoreLocked();
+                            }
+                            break;
                         default:
                             if (name.startsWith(KEY_PREFIX_STANDBY_QUOTA) && !standbyQuotaUpdated) {
                                 // The quotas need to be updated in order, so we can't just rely
@@ -551,6 +570,15 @@
             }
         }
 
+        private void migrateAlarmsToNewStoreLocked() {
+            final AlarmStore newStore = LAZY_BATCHING ? new LazyAlarmStore()
+                    : new BatchingAlarmStore();
+            final ArrayList<Alarm> allAlarms = mAlarmStore.remove((unused) -> true);
+            newStore.addAll(allAlarms);
+            mAlarmStore = newStore;
+            mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
+        }
+
         private void updateStandbyQuotasLocked() {
             // The bucket quotas need to be read as an atomic unit but the properties passed to
             // onPropertiesChanged may only have one key populated at a time.
@@ -659,6 +687,9 @@
             TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw);
             pw.println();
 
+            pw.print(KEY_LAZY_BATCHING, LAZY_BATCHING);
+            pw.println();
+
             pw.decreaseIndent();
         }
 
@@ -770,7 +801,7 @@
     // minimum recurrence period or alarm futurity for us to be able to fuzz it
     static final long MIN_FUZZABLE_INTERVAL = 10000;
     @GuardedBy("mLock")
-    final AlarmStore mAlarmStore;
+    AlarmStore mAlarmStore;
 
     // set to non-null if in idle mode; while in this mode, any alarms we don't want
     // to run during this time are rescehduled to go off after this alarm.
@@ -781,7 +812,6 @@
     AlarmManagerService(Context context, Injector injector) {
         super(context);
         mInjector = injector;
-        mAlarmStore = new BatchingAlarmStore(() -> mNextAlarmClockMayChange = true);
     }
 
     public AlarmManagerService(Context context) {
@@ -1219,6 +1249,11 @@
         synchronized (mLock) {
             mHandler = new AlarmHandler();
             mConstants = new Constants();
+
+            mAlarmStore = mConstants.LAZY_BATCHING ? new LazyAlarmStore()
+                    : new BatchingAlarmStore();
+            mAlarmStore.setAlarmClockRemovalListener(mAlarmClockUpdater);
+
             mAppWakeupHistory = new AppWakeupHistory(Constants.DEFAULT_APP_STANDBY_WINDOW);
 
             mNextWakeup = mNextNonWakeup = 0;
@@ -3055,12 +3090,13 @@
 
     static final void dumpAlarmList(IndentingPrintWriter ipw, ArrayList<Alarm> list,
             long nowELAPSED, SimpleDateFormat sdf) {
-        for (int i = list.size() - 1; i >= 0; i--) {
+        final int n = list.size();
+        for (int i = n - 1; i >= 0; i--) {
             final Alarm a = list.get(i);
             final String label = Alarm.typeToString(a.type);
             ipw.print(label);
             ipw.print(" #");
-            ipw.print(i);
+            ipw.print(n - i);
             ipw.print(": ");
             ipw.println(a);
             ipw.increaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
index 7a846b9..0e442d0 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmStore.java
@@ -40,6 +40,13 @@
     void add(Alarm a);
 
     /**
+     * Adds all the given alarms to this store.
+     *
+     * @param alarms The alarms to add.
+     */
+    void addAll(ArrayList<Alarm> alarms);
+
+    /**
      * Removes alarms that pass the given predicate.
      *
      * @param whichAlarms The predicate describing the alarms to remove.
@@ -48,11 +55,17 @@
     ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms);
 
     /**
+     * Set a listener to be invoked whenever an alarm clock is removed by a call to
+     * {@link #remove(Predicate) remove} from this store.
+     */
+    void setAlarmClockRemovalListener(Runnable listener);
+
+    /**
      * Gets the earliest alarm with the flag {@link android.app.AlarmManager#FLAG_WAKE_FROM_IDLE}
      * based on {@link Alarm#getWhenElapsed()}.
      *
      * @return An alarm object matching the description above or {@code null} if no such alarm was
-     *         found.
+     * found.
      */
     Alarm getNextWakeFromIdleAlarm();
 
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
index cbfe80b..e7edfb7 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/BatchingAlarmStore.java
@@ -41,45 +41,22 @@
  */
 public class BatchingAlarmStore implements AlarmStore {
 
-    private ArrayList<Batch> mAlarmBatches = new ArrayList<>();
+    private final ArrayList<Batch> mAlarmBatches = new ArrayList<>();
     private int mSize;
-    private AlarmClockRemovalListener mAlarmClockRemovalListener;
+    private Runnable mOnAlarmClockRemoved;
 
     interface Stats {
         int REBATCH_ALL_ALARMS = 0;
     }
 
-    final StatLogger mStatLogger = new StatLogger("Alarm store stats", new String[]{
+    final StatLogger mStatLogger = new StatLogger("BatchingAlarmStore stats", new String[]{
             "REBATCH_ALL_ALARMS",
     });
 
-    private static final Comparator<Batch> sBatchOrder = (b1, b2) -> {
-        long when1 = b1.mStart;
-        long when2 = b2.mStart;
-        if (when1 > when2) {
-            return 1;
-        }
-        if (when1 < when2) {
-            return -1;
-        }
-        return 0;
-    };
+    private static final Comparator<Batch> sBatchOrder = Comparator.comparingLong(b -> b.mStart);
 
-    private static final Comparator<Alarm> sIncreasingTimeOrder = (a1, a2) -> {
-        long when1 = a1.getWhenElapsed();
-        long when2 = a2.getWhenElapsed();
-        if (when1 > when2) {
-            return 1;
-        }
-        if (when1 < when2) {
-            return -1;
-        }
-        return 0;
-    };
-
-    BatchingAlarmStore(AlarmClockRemovalListener listener) {
-        mAlarmClockRemovalListener = listener;
-    }
+    private static final Comparator<Alarm> sIncreasingTimeOrder = Comparator.comparingLong(
+            Alarm::getWhenElapsed);
 
     @Override
     public void add(Alarm a) {
@@ -88,6 +65,16 @@
     }
 
     @Override
+    public void addAll(ArrayList<Alarm> alarms) {
+        if (alarms == null) {
+            return;
+        }
+        for (final Alarm a : alarms) {
+            add(a);
+        }
+    }
+
+    @Override
     public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) {
         final ArrayList<Alarm> removed = new ArrayList<>();
         for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
@@ -106,6 +93,11 @@
     }
 
     @Override
+    public void setAlarmClockRemovalListener(Runnable listener) {
+        mOnAlarmClockRemoved = listener;
+    }
+
+    @Override
     public Alarm getNextWakeFromIdleAlarm() {
         for (final Batch batch : mAlarmBatches) {
             if ((batch.mFlags & AlarmManager.FLAG_WAKE_FROM_IDLE) == 0) {
@@ -317,8 +309,8 @@
                 Alarm alarm = mAlarms.get(i);
                 if (predicate.test(alarm)) {
                     removed.add(mAlarms.remove(i));
-                    if (alarm.alarmClock != null && mAlarmClockRemovalListener != null) {
-                        mAlarmClockRemovalListener.onRemoved();
+                    if (alarm.alarmClock != null && mOnAlarmClockRemoved != null) {
+                        mOnAlarmClockRemoved.run();
                     }
                     if (isTimeTickAlarm(alarm)) {
                         // This code path is not invoked when delivering alarms, only when removing
@@ -388,9 +380,4 @@
             proto.end(token);
         }
     }
-
-    @FunctionalInterface
-    interface AlarmClockRemovalListener {
-        void onRemoved();
-    }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
new file mode 100644
index 0000000..8ca1446
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
@@ -0,0 +1,217 @@
+/*
+ * 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.server.alarm;
+
+import static com.android.server.alarm.AlarmManagerService.TAG;
+import static com.android.server.alarm.AlarmManagerService.dumpAlarmList;
+import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm;
+
+import android.app.AlarmManager;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.StatLogger;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.function.Predicate;
+
+/**
+ * Lazy implementation of an alarm store.
+ * This keeps the alarms in a sorted list, and only batches them at the time of delivery.
+ */
+public class LazyAlarmStore implements AlarmStore {
+
+    private final ArrayList<Alarm> mAlarms = new ArrayList<>();
+    private Runnable mOnAlarmClockRemoved;
+
+    interface Stats {
+        int GET_NEXT_DELIVERY_TIME = 0;
+        int GET_NEXT_WAKEUP_DELIVERY_TIME = 1;
+    }
+
+    final StatLogger mStatLogger = new StatLogger("LazyAlarmStore stats", new String[]{
+            "GET_NEXT_DELIVERY_TIME",
+            "GET_NEXT_WAKEUP_DELIVERY_TIME",
+    });
+
+    // Decreasing time order because it is more efficient to remove from the tail of an array list.
+    private static final Comparator<Alarm> sDecreasingTimeOrder = Comparator.comparingLong(
+            Alarm::getWhenElapsed).reversed();
+
+    @Override
+    public void add(Alarm a) {
+        int index = Collections.binarySearch(mAlarms, a, sDecreasingTimeOrder);
+        if (index < 0) {
+            index = 0 - index - 1;
+        }
+        mAlarms.add(index, a);
+    }
+
+    @Override
+    public void addAll(ArrayList<Alarm> alarms) {
+        if (alarms == null) {
+            return;
+        }
+        mAlarms.addAll(alarms);
+        Collections.sort(alarms, sDecreasingTimeOrder);
+    }
+
+    @Override
+    public ArrayList<Alarm> remove(Predicate<Alarm> whichAlarms) {
+        final ArrayList<Alarm> removedAlarms = new ArrayList<>();
+        for (int i = mAlarms.size() - 1; i >= 0; i--) {
+            if (whichAlarms.test(mAlarms.get(i))) {
+                final Alarm removed = mAlarms.remove(i);
+                if (removed.alarmClock != null && mOnAlarmClockRemoved != null) {
+                    mOnAlarmClockRemoved.run();
+                }
+                if (isTimeTickAlarm(removed)) {
+                    // This code path is not invoked when delivering alarms, only when removing
+                    // alarms due to the caller cancelling it or getting uninstalled, etc.
+                    Slog.wtf(TAG, "Removed TIME_TICK alarm");
+                }
+                removedAlarms.add(removed);
+            }
+        }
+        return removedAlarms;
+    }
+
+    @Override
+    public void setAlarmClockRemovalListener(Runnable listener) {
+        mOnAlarmClockRemoved = listener;
+    }
+
+    @Override
+    public Alarm getNextWakeFromIdleAlarm() {
+        for (int i = mAlarms.size() - 1; i >= 0; i--) {
+            final Alarm alarm = mAlarms.get(i);
+            if ((alarm.flags & AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
+                return alarm;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public int size() {
+        return mAlarms.size();
+    }
+
+    @Override
+    public long getNextWakeupDeliveryTime() {
+        final long start = mStatLogger.getTime();
+        long nextWakeup = 0;
+        for (int i = mAlarms.size() - 1; i >= 0; i--) {
+            final Alarm a = mAlarms.get(i);
+            if (!a.wakeup) {
+                continue;
+            }
+            if (nextWakeup == 0) {
+                nextWakeup = a.getMaxWhenElapsed();
+            } else {
+                if (a.getWhenElapsed() > nextWakeup) {
+                    break;
+                }
+                nextWakeup = Math.min(nextWakeup, a.getMaxWhenElapsed());
+            }
+        }
+        mStatLogger.logDurationStat(Stats.GET_NEXT_WAKEUP_DELIVERY_TIME, start);
+        return nextWakeup;
+    }
+
+    @Override
+    public long getNextDeliveryTime() {
+        final long start = mStatLogger.getTime();
+        final int n = mAlarms.size();
+        if (n == 0) {
+            return 0;
+        }
+        long nextDelivery = mAlarms.get(n - 1).getMaxWhenElapsed();
+        for (int i = n - 2; i >= 0; i--) {
+            final Alarm a = mAlarms.get(i);
+            if (a.getWhenElapsed() > nextDelivery) {
+                break;
+            }
+            nextDelivery = Math.min(nextDelivery, a.getMaxWhenElapsed());
+        }
+        mStatLogger.logDurationStat(Stats.GET_NEXT_DELIVERY_TIME, start);
+        return nextDelivery;
+    }
+
+    @Override
+    public ArrayList<Alarm> removePendingAlarms(long nowElapsed) {
+        final ArrayList<Alarm> pending = new ArrayList<>();
+        final ArrayList<Alarm> standAlones = new ArrayList<>();
+
+        for (int i = mAlarms.size() - 1; i >= 0; i--) {
+            final Alarm alarm = mAlarms.get(i);
+            if (alarm.getWhenElapsed() > nowElapsed) {
+                break;
+            }
+            pending.add(alarm);
+            if ((alarm.flags & AlarmManager.FLAG_STANDALONE) != 0) {
+                standAlones.add(alarm);
+            }
+        }
+        if (!standAlones.isEmpty()) {
+            // If there are deliverable standalone alarms, others must not go out yet.
+            mAlarms.removeAll(standAlones);
+            return standAlones;
+        }
+        mAlarms.removeAll(pending);
+        return pending;
+    }
+
+    @Override
+    public boolean updateAlarmDeliveries(AlarmDeliveryCalculator deliveryCalculator) {
+        boolean changed = false;
+        for (final Alarm alarm : mAlarms) {
+            changed |= deliveryCalculator.updateAlarmDelivery(alarm);
+        }
+        if (changed) {
+            Collections.sort(mAlarms, sDecreasingTimeOrder);
+        }
+        return changed;
+    }
+
+    @Override
+    public ArrayList<Alarm> asList() {
+        final ArrayList<Alarm> copy = new ArrayList<>(mAlarms);
+        Collections.reverse(copy);
+        return copy;
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter ipw, long nowElapsed, SimpleDateFormat sdf) {
+        ipw.println(mAlarms.size() + " pending alarms: ");
+        ipw.increaseIndent();
+        dumpAlarmList(ipw, mAlarms, nowElapsed, sdf);
+        ipw.decreaseIndent();
+        mStatLogger.dump(ipw);
+    }
+
+    @Override
+    public void dumpProto(ProtoOutputStream pos, long nowElapsed) {
+        for (final Alarm a : mAlarms) {
+            a.dumpDebug(pos, AlarmManagerServiceDumpProto.PENDING_ALARMS, nowElapsed);
+        }
+    }
+}
diff --git a/api/current.txt b/api/current.txt
index fbfce3c..93eb32c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5974,6 +5974,7 @@
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
+    method public boolean isConversation();
     method public boolean isDemoted();
     method public boolean isImportantConversation();
     method public void setAllowBubbles(boolean);
@@ -10682,8 +10683,6 @@
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
-    field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
-    field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -12311,9 +12310,6 @@
     field public static final int SYNCHRONOUS = 2; // 0x2
     field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
     field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
-    field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
-    field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
-    field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
@@ -16430,6 +16426,7 @@
     method public void getMetrics(@NonNull android.graphics.Paint, @Nullable android.graphics.Paint.FontMetrics);
     method @NonNull public android.graphics.fonts.FontStyle getStyle();
     method @IntRange(from=0) public int getTtcIndex();
+    method public boolean isSameSource(@NonNull android.graphics.fonts.Font);
   }
 
   public static final class Font.Builder {
@@ -25277,6 +25274,7 @@
   public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
     ctor public MediaCas(@NonNull android.content.Context, int, @Nullable String, int) throws android.media.MediaCasException.UnsupportedCasException;
+    ctor public MediaCas(@NonNull android.content.Context, int, @Nullable String, int, @Nullable android.os.Handler, @Nullable android.media.MediaCas.EventListener) throws android.media.MediaCasException.UnsupportedCasException;
     method public void close();
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method protected void finalize();
@@ -31408,6 +31406,7 @@
     field @Deprecated public String FQDN;
     field @Deprecated public static final int SECURITY_TYPE_EAP = 3; // 0x3
     field @Deprecated public static final int SECURITY_TYPE_EAP_SUITE_B = 5; // 0x5
+    field @Deprecated public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9; // 0x9
     field @Deprecated public static final int SECURITY_TYPE_OPEN = 0; // 0x0
     field @Deprecated public static final int SECURITY_TYPE_OWE = 6; // 0x6
     field @Deprecated public static final int SECURITY_TYPE_PSK = 2; // 0x2
diff --git a/api/system-current.txt b/api/system-current.txt
index 9cf0926..115b4d8 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5090,6 +5090,7 @@
     field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
     field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
     field public static final long INVALID_FILTER_ID_64BIT = -1L; // 0xffffffffffffffffL
+    field public static final int INVALID_FIRST_MACROBLOCK_IN_SLICE = -1; // 0xffffffff
     field public static final int INVALID_FRONTEND_SETTING_FREQUENCY = -1; // 0xffffffff
     field public static final int INVALID_LTS_ID = -1; // 0xffffffff
     field public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM = -1; // 0xffffffff
@@ -5390,6 +5391,7 @@
 
   public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
+    method public int getFirstMbInSlice();
     method public int getMpuSequenceNumber();
     method public long getPts();
     method public int getScHevcIndexMask();
@@ -5430,9 +5432,14 @@
     field public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA = 128; // 0x80
     field public static final int SC_HEVC_INDEX_SPS = 1; // 0x1
     field public static final int SC_INDEX_B_FRAME = 4; // 0x4
+    field public static final int SC_INDEX_B_SLICE = 64; // 0x40
     field public static final int SC_INDEX_I_FRAME = 1; // 0x1
+    field public static final int SC_INDEX_I_SLICE = 16; // 0x10
     field public static final int SC_INDEX_P_FRAME = 2; // 0x2
+    field public static final int SC_INDEX_P_SLICE = 32; // 0x20
     field public static final int SC_INDEX_SEQUENCE = 8; // 0x8
+    field public static final int SC_INDEX_SI_SLICE = 128; // 0x80
+    field public static final int SC_INDEX_SP_SLICE = 256; // 0x100
     field public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = 4096; // 0x1000
     field public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = 8; // 0x8
     field public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = 4; // 0x4
@@ -5553,6 +5560,7 @@
 
   public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
+    method public int getFirstMbInSlice();
     method public int getPacketId();
     method public long getPts();
     method public int getScIndexMask();
@@ -5878,6 +5886,7 @@
   public class DvbsFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
     method @NonNull public static android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder builder();
     method @Nullable public android.media.tv.tuner.frontend.DvbsCodeRate getCodeRate();
+    method public boolean getCouldHandleDiseqcRxMessage();
     method public int getInputStreamId();
     method public int getModulation();
     method public int getPilot();
@@ -5887,7 +5896,6 @@
     method public int getSymbolRate();
     method public int getType();
     method public int getVcmMode();
-    method public boolean isDiseqcRxMessage();
     field public static final int MODULATION_AUTO = 1; // 0x1
     field public static final int MODULATION_MOD_128APSK = 2048; // 0x800
     field public static final int MODULATION_MOD_16APSK = 256; // 0x100
@@ -5931,7 +5939,7 @@
   public static class DvbsFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setCodeRate(@Nullable android.media.tv.tuner.frontend.DvbsCodeRate);
-    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setDiseqcRxMessage(boolean);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setCouldHandleDiseqcRxMessage(boolean);
     method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setInputStreamId(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setModulation(int);
@@ -6071,7 +6079,7 @@
   }
 
   public abstract class FrontendSettings {
-    method public int getEndFrequency();
+    method @IntRange(from=1) public int getEndFrequency();
     method public int getFrequency();
     method public int getFrontendSpectralInversion();
     method public abstract int getType();
@@ -6353,6 +6361,7 @@
   public interface ScanCallback {
     method public void onAnalogSifStandardReported(int);
     method public void onAtsc3PlpInfosReported(@NonNull android.media.tv.tuner.frontend.Atsc3PlpInfo[]);
+    method public default void onDvbcAnnexReported(int);
     method public void onDvbsStandardReported(int);
     method public void onDvbtStandardReported(int);
     method public void onFrequenciesReported(@NonNull int[]);
@@ -12337,6 +12346,17 @@
 
 package android.telephony.ims {
 
+  public final class AudioCodecAttributes implements android.os.Parcelable {
+    ctor public AudioCodecAttributes(float, @NonNull android.util.Range<java.lang.Float>, float, @NonNull android.util.Range<java.lang.Float>);
+    method public int describeContents();
+    method public float getBandwidthKhz();
+    method @NonNull public android.util.Range<java.lang.Float> getBandwidthRangeKhz();
+    method public float getBitrateKbps();
+    method @NonNull public android.util.Range<java.lang.Float> getBitrateRangeKbps();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.AudioCodecAttributes> CREATOR;
+  }
+
   public final class ImsCallForwardInfo implements android.os.Parcelable {
     ctor public ImsCallForwardInfo(int, int, int, int, @NonNull String, int);
     method public int describeContents();
@@ -12705,6 +12725,7 @@
     ctor public ImsStreamMediaProfile(int, int, int, int, int);
     method public void copyFrom(android.telephony.ims.ImsStreamMediaProfile);
     method public int describeContents();
+    method @Nullable public android.telephony.ims.AudioCodecAttributes getAudioCodecAttributes();
     method public int getAudioDirection();
     method public int getAudioQuality();
     method public int getRttMode();
@@ -12712,6 +12733,7 @@
     method public int getVideoQuality();
     method public boolean isReceivingRttAudio();
     method public boolean isRttCall();
+    method public void setAudioCodecAttributes(@NonNull android.telephony.ims.AudioCodecAttributes);
     method public void setReceivingRttAudio(boolean);
     method public void setRttMode(int);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java
index 8683ca1..a0361d0 100644
--- a/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java
+++ b/cmds/requestsync/src/com/android/commands/requestsync/RequestSync.java
@@ -294,9 +294,9 @@
                 "       -a|--authority <AUTHORITY>\n" +
                 "    App-standby related options\n" +
                 "\n" +
-                "       -f|--foreground (cause WORKING_SET, FREQUENT sync adapters" +
-                        " to run immediately)\n" +
-                "       -F|--top (cause even RARE sync adapters to run immediately)\n" +
+                "       -f|--foreground (defeat app-standby job throttling," +
+                " but not battery saver)\n" +
+                "       -F|--top (defeat app-standby job throttling and battery saver)\n" +
                 "    ContentResolver extra options:\n" +
                 "      --is|--ignore-settings: Add SYNC_EXTRAS_IGNORE_SETTINGS\n" +
                 "      --ib|--ignore-backoff: Add SYNC_EXTRAS_IGNORE_BACKOFF\n" +
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index ac2a8e4..b270a52 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -502,6 +502,7 @@
         MediametricsMediaParserReported mediametrics_mediaparser_reported = 316;
         TlsHandshakeReported tls_handshake_reported = 317 [(module) = "conscrypt"];
         TextClassifierApiUsageReported text_classifier_api_usage_reported = 318  [(module) = "textclassifier"];
+        KilledAppStatsReported killed_app_stats_reported = 319 [(module) = "carwatchdogd"];
 
         // StatsdStats tracks platform atoms with ids upto 500.
         // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
@@ -4280,9 +4281,16 @@
     optional android.stats.sysui.NotificationImportance old_importance = 5;
     // New importance setting
     optional android.stats.sysui.NotificationImportance importance = 6;
+    // whether or not this channel represents a conversation
+    optional bool is_conversation = 7;
+    // Hash of app-assigned notification conversation id
+    optional int32 conversation_id_hash = 8;
+    // whether or not the user demoted this channel out of the conversation space
+    optional bool is_conversation_demoted = 9;
+    // whether this conversation is marked as being a priority
+    optional bool is_conversation_priority = 10;
 }
 
-
 /**
  * Logs when a biometric acquire event occurs.
  *
@@ -5304,6 +5312,11 @@
         LAUNCHER_APP_CLOSE_TO_HOME = 10;
         LAUNCHER_APP_CLOSE_TO_PIP = 11;
         LAUNCHER_QUICK_SWITCH = 12;
+        SHADE_HEADS_UP_APPEAR = 13;
+        SHADE_HEADS_UP_DISAPPEAR = 14;
+        SHADE_NOTIFICATION_ADD = 15;
+        SHADE_NOTIFICATION_REMOVE = 16;
+        SHADE_APP_LAUNCH = 17;
     }
 
     optional InteractionType interaction_type = 1;
@@ -12067,3 +12080,143 @@
     optional ResultType result_type = 2;
     optional int64 latency_millis = 3;
 }
+
+/**
+ * Logs the current state of an application before it is killed.
+ *
+ * Pushed from:
+ *  packages/services/Car/watchdog/server/src/ApplicationTerminator.cpp
+ */
+message KilledAppStatsReported {
+    // Linux process uid for the package.
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Name of the package that was killed.
+    optional string package_name = 2;
+
+    // State of the application when it was killed.
+    enum AppState {
+        UNKNOWN_APP_STATE = 0;
+        BACKGROUND = 1;
+        FOREGROUND = 2;
+    }
+    optional AppState app_state = 3;
+
+    // System state indicating whether the system was in normal mode or garage mode.
+    enum SystemState {
+        UNKNOWN_SYSTEM_STATE = 0;
+        USER_INTERACTION_MODE = 1;
+        NO_USER_INTERACTION_MODE = 2;
+    }
+    optional SystemState system_state = 4;
+
+    // Reason for killing the application.
+    // Keep in sync between:
+    //   packages/services/Car/watchdog/server/src/ApplicationTerminator.h
+    //   frameworks/base/cmds/statsd/src/atoms.proto
+    enum KillReason {
+        UNKNOWN_KILL_REASON = 0;
+        KILLED_ON_ANR = 1;
+        KILLED_ON_IO_OVERUSE = 2;
+        KILLED_ON_MEMORY_OVERUSE = 3;
+    }
+    optional KillReason kill_reason = 5;
+
+    // Stats of the processes owned by the application when the application was killed.
+    // The process stack traces are not collected when the application was killed due to IO_OVERUSE.
+    optional ProcessStats process_stat = 6 [(log_mode) = MODE_BYTES];
+
+    // The application's I/O overuse stats logged only when the kill reason is KILLED_ON_IO_OVERUSE.
+    optional IoOveruseStats io_overuse_stats = 7 [(log_mode) = MODE_BYTES];
+}
+
+/**
+ * Logs I/O overuse stats for a package.
+ *
+ * Keep in sync between:
+ *  packages/services/Car/watchdog/server/src/proto/statsd.proto
+ *  frameworks/base/cmds/statsd/src/atoms.proto
+ *
+ * Logged from:
+ *  packages/services/Car/watchdog/server/src/ApplicationTerminator.cpp
+ */
+message IoOveruseStats {
+    enum Period {
+        DAILY = 0;
+        WEEKLY = 1;
+    }
+
+    // Threshold and usage stats period.
+    optional Period period = 1;
+
+    // Threshold in-terms of write bytes defined for the package.
+    optional PerStateBytes threshold = 2;
+
+    // Number of write bytes in each state for the specified period.
+    optional PerStateBytes written_bytes = 3;
+};
+
+/**
+ * Logs bytes attributed to each application and system states.
+ *
+ * Keep in sync between:
+ *  packages/services/Car/watchdog/server/src/proto/statsd.proto
+ *  frameworks/base/cmds/statsd/src/atoms.proto
+ *
+ * Logged from:
+ *  packages/services/Car/watchdog/server/src/ApplicationTerminator.cpp
+ */
+message PerStateBytes {
+    // Number of bytes attributed to the application foreground.
+    optional int64 foreground_bytes = 1;
+
+    // Number of bytes attributed to the application background.
+    optional int64 background_bytes = 2;
+
+    // Number of bytes attributed to the garage mode.
+    optional int64 garage_mode_bytes = 3;
+}
+
+/**
+ * Logs each ProcessStat in ProcessStats.
+ * Keep in sync between:
+ *  packages/services/Car/watchdog/server/src/proto/statsd.proto
+ *  frameworks/base/cmds/statsd/src/atoms.proto
+ * Logged from:
+ *  packages/services/Car/watchdog/server/src/ApplicationTerminator.cpp
+ */
+message ProcessStats {
+    // Records the stats of the processes owned by an application.
+    repeated ProcessStat process_stat = 1;
+}
+
+/**
+ * Logs a process's stats.
+ * Keep in sync between:
+ *  packages/services/Car/watchdog/server/src/proto/statsd.proto
+ *  frameworks/base/cmds/statsd/src/atoms.proto
+ * Logged from:
+ *  packages/services/Car/watchdog/server/src/ApplicationTerminator.cpp
+ */
+message ProcessStat {
+    // Command name of the process.
+    optional string process_name = 1;
+
+    // Process uptime.
+    optional uint64 uptime_milliseconds = 2;
+
+    // Number of major page faults caused by the process and its children.
+    optional uint64 major_page_faults = 3;
+
+    // Peak virtual memory size in kb.
+    optional uint64 vm_peak_kb = 4;
+
+    // Virtual memory size in kb.
+    optional uint64 vm_size_kb = 5;
+
+    // Peak resident set size (high water mark) in kb.
+    optional uint64 vm_hwm_kb = 6;
+
+    // Resident set size in kb.
+    optional uint64 vm_rss_kb = 7;
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index f85005b..9329e2a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5974,6 +5974,7 @@
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
+    method public boolean isConversation();
     method public boolean isDemoted();
     method public boolean isImportantConversation();
     method public void setAllowBubbles(boolean);
@@ -10682,8 +10683,6 @@
     field public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED";
     field public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
     field public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED";
-    field public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
-    field public static final String ACTION_PACKAGE_UNSTARTABLE = "android.intent.action.PACKAGE_UNSTARTABLE";
     field public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
     field public static final String ACTION_PASTE = "android.intent.action.PASTE";
     field public static final String ACTION_PICK = "android.intent.action.PICK";
@@ -12311,9 +12310,6 @@
     field public static final int SYNCHRONOUS = 2; // 0x2
     field @Nullable public static final java.util.List<java.security.cert.Certificate> TRUST_ALL;
     field @NonNull public static final java.util.List<java.security.cert.Certificate> TRUST_NONE;
-    field public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1; // 0x1
-    field public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2; // 0x2
-    field public static final int UNSTARTABLE_REASON_UNKNOWN = 0; // 0x0
     field public static final int VERIFICATION_ALLOW = 1; // 0x1
     field public static final int VERIFICATION_REJECT = -1; // 0xffffffff
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
@@ -16412,6 +16408,7 @@
     method public void getMetrics(@NonNull android.graphics.Paint, @Nullable android.graphics.Paint.FontMetrics);
     method @NonNull public android.graphics.fonts.FontStyle getStyle();
     method @IntRange(from=0) public int getTtcIndex();
+    method public boolean isSameSource(@NonNull android.graphics.fonts.Font);
   }
 
   public static final class Font.Builder {
@@ -25259,6 +25256,7 @@
   public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
     ctor public MediaCas(@NonNull android.content.Context, int, @Nullable String, int) throws android.media.MediaCasException.UnsupportedCasException;
+    ctor public MediaCas(@NonNull android.content.Context, int, @Nullable String, int, @Nullable android.os.Handler, @Nullable android.media.MediaCas.EventListener) throws android.media.MediaCasException.UnsupportedCasException;
     method public void close();
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method protected void finalize();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b37f738..1e3a2c0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -5030,6 +5030,7 @@
     field public static final int INVALID_AV_SYNC_ID = -1; // 0xffffffff
     field public static final int INVALID_FILTER_ID = -1; // 0xffffffff
     field public static final long INVALID_FILTER_ID_64BIT = -1L; // 0xffffffffffffffffL
+    field public static final int INVALID_FIRST_MACROBLOCK_IN_SLICE = -1; // 0xffffffff
     field public static final int INVALID_FRONTEND_SETTING_FREQUENCY = -1; // 0xffffffff
     field public static final int INVALID_LTS_ID = -1; // 0xffffffff
     field public static final int INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM = -1; // 0xffffffff
@@ -5330,6 +5331,7 @@
 
   public class MmtpRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
+    method public int getFirstMbInSlice();
     method public int getMpuSequenceNumber();
     method public long getPts();
     method public int getScHevcIndexMask();
@@ -5370,9 +5372,14 @@
     field public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA = 128; // 0x80
     field public static final int SC_HEVC_INDEX_SPS = 1; // 0x1
     field public static final int SC_INDEX_B_FRAME = 4; // 0x4
+    field public static final int SC_INDEX_B_SLICE = 64; // 0x40
     field public static final int SC_INDEX_I_FRAME = 1; // 0x1
+    field public static final int SC_INDEX_I_SLICE = 16; // 0x10
     field public static final int SC_INDEX_P_FRAME = 2; // 0x2
+    field public static final int SC_INDEX_P_SLICE = 32; // 0x20
     field public static final int SC_INDEX_SEQUENCE = 8; // 0x8
+    field public static final int SC_INDEX_SI_SLICE = 128; // 0x80
+    field public static final int SC_INDEX_SP_SLICE = 256; // 0x100
     field public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG = 4096; // 0x1000
     field public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED = 8; // 0x8
     field public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED = 4; // 0x4
@@ -5493,6 +5500,7 @@
 
   public class TsRecordEvent extends android.media.tv.tuner.filter.FilterEvent {
     method public long getDataLength();
+    method public int getFirstMbInSlice();
     method public int getPacketId();
     method public long getPts();
     method public int getScIndexMask();
@@ -5818,6 +5826,7 @@
   public class DvbsFrontendSettings extends android.media.tv.tuner.frontend.FrontendSettings {
     method @NonNull public static android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder builder();
     method @Nullable public android.media.tv.tuner.frontend.DvbsCodeRate getCodeRate();
+    method public boolean getCouldHandleDiseqcRxMessage();
     method public int getInputStreamId();
     method public int getModulation();
     method public int getPilot();
@@ -5827,7 +5836,6 @@
     method public int getSymbolRate();
     method public int getType();
     method public int getVcmMode();
-    method public boolean isDiseqcRxMessage();
     field public static final int MODULATION_AUTO = 1; // 0x1
     field public static final int MODULATION_MOD_128APSK = 2048; // 0x800
     field public static final int MODULATION_MOD_16APSK = 256; // 0x100
@@ -5871,7 +5879,7 @@
   public static class DvbsFrontendSettings.Builder {
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings build();
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setCodeRate(@Nullable android.media.tv.tuner.frontend.DvbsCodeRate);
-    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setDiseqcRxMessage(boolean);
+    method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setCouldHandleDiseqcRxMessage(boolean);
     method @IntRange(from=1) @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setFrequency(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setInputStreamId(int);
     method @NonNull public android.media.tv.tuner.frontend.DvbsFrontendSettings.Builder setModulation(int);
@@ -6011,7 +6019,7 @@
   }
 
   public abstract class FrontendSettings {
-    method public int getEndFrequency();
+    method @IntRange(from=1) public int getEndFrequency();
     method public int getFrequency();
     method public int getFrontendSpectralInversion();
     method public abstract int getType();
@@ -6293,6 +6301,7 @@
   public interface ScanCallback {
     method public void onAnalogSifStandardReported(int);
     method public void onAtsc3PlpInfosReported(@NonNull android.media.tv.tuner.frontend.Atsc3PlpInfo[]);
+    method public default void onDvbcAnnexReported(int);
     method public void onDvbsStandardReported(int);
     method public void onDvbtStandardReported(int);
     method public void onFrequenciesReported(@NonNull int[]);
@@ -11179,6 +11188,17 @@
 
 package android.telephony.ims {
 
+  public final class AudioCodecAttributes implements android.os.Parcelable {
+    ctor public AudioCodecAttributes(float, @NonNull android.util.Range<java.lang.Float>, float, @NonNull android.util.Range<java.lang.Float>);
+    method public int describeContents();
+    method public float getBandwidthKhz();
+    method @NonNull public android.util.Range<java.lang.Float> getBandwidthRangeKhz();
+    method public float getBitrateKbps();
+    method @NonNull public android.util.Range<java.lang.Float> getBitrateRangeKbps();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.AudioCodecAttributes> CREATOR;
+  }
+
   public final class ImsCallForwardInfo implements android.os.Parcelable {
     ctor public ImsCallForwardInfo(int, int, int, int, @NonNull String, int);
     method public int describeContents();
@@ -11547,6 +11567,7 @@
     ctor public ImsStreamMediaProfile(int, int, int, int, int);
     method public void copyFrom(android.telephony.ims.ImsStreamMediaProfile);
     method public int describeContents();
+    method @Nullable public android.telephony.ims.AudioCodecAttributes getAudioCodecAttributes();
     method public int getAudioDirection();
     method public int getAudioQuality();
     method public int getRttMode();
@@ -11554,6 +11575,7 @@
     method public int getVideoQuality();
     method public boolean isReceivingRttAudio();
     method public boolean isRttCall();
+    method public void setAudioCodecAttributes(@NonNull android.telephony.ims.AudioCodecAttributes);
     method public void setReceivingRttAudio(boolean);
     method public void setRttMode(int);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2ac345d..29e407b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -77,6 +77,7 @@
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager.ServiceNotFoundException;
@@ -8722,13 +8723,16 @@
      * the activity is visible after the screen is turned on when the lockscreen is up. In addition,
      * if this flag is set and the activity calls {@link
      * KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)}
-     * the screen will turn on.
+     * the screen will turn on. If the screen is off and device is not secured, this flag can turn
+     * screen on and dismiss keyguard to make this activity visible and resume, which can be used to
+     * replace {@link PowerManager#ACQUIRE_CAUSES_WAKEUP}
      *
      * @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise.
      *
      * @see #setShowWhenLocked(boolean)
      * @see android.R.attr#turnScreenOn
      * @see android.R.attr#showWhenLocked
+     * @see KeyguardManager#isDeviceSecure()
      */
     public void setTurnScreenOn(boolean turnScreenOn) {
         try {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b2a039f..ba57370 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.windowingModeToString;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 
@@ -1741,11 +1743,6 @@
          * @hide
          */
         public void dump(PrintWriter pw, String indent) {
-            final String activityType = WindowConfiguration.activityTypeToString(
-                    configuration.windowConfiguration.getActivityType());
-            final String windowingMode = WindowConfiguration.activityTypeToString(
-                    configuration.windowConfiguration.getActivityType());
-
             pw.println(); pw.print("   ");
             pw.print(" id="); pw.print(persistentId);
             pw.print(" stackId="); pw.print(stackId);
@@ -1771,8 +1768,8 @@
             pw.print("   ");
             pw.print(" isExcluded=");
             pw.print(((baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0));
-            pw.print(" activityType="); pw.print(activityType);
-            pw.print(" windowingMode="); pw.print(windowingMode);
+            pw.print(" activityType="); pw.print(activityTypeToString(getActivityType()));
+            pw.print(" windowingMode="); pw.print(windowingModeToString(getWindowingMode()));
             pw.print(" supportsSplitScreenMultiWindow=");
             pw.println(supportsSplitScreenMultiWindow);
             if (taskDescription != null) {
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index b609462..4eef616 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -286,7 +286,7 @@
         return mSecurityViolation;
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @UnsupportedAppUsage(trackingBug = 172409979)
     public CompatibilityInfo getCompatibilityInfo() {
         return mDisplayAdjustments.getCompatibilityInfo();
     }
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index a06ffbd..080aac9 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -629,12 +629,20 @@
     }
 
     /**
+     * Whether or not this channel represents a conversation.
+     */
+    public boolean isConversation() {
+        return !TextUtils.isEmpty(getConversationId());
+    }
+
+
+    /**
      * Whether or not notifications in this conversation are considered important.
      *
      * <p>Important conversations may get special visual treatment, and might be able to bypass DND.
      *
-     * <p>This is only valid for channels that represent conversations, that is, those with a valid
-     * {@link #getConversationId() conversation id}.
+     * <p>This is only valid for channels that represent conversations, that is,
+     * where {@link #isConversation()} is true.
      */
     public boolean isImportantConversation() {
         return mImportantConvo;
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 43b7722c..5dfab6e 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -257,6 +257,18 @@
     }
 
     /** @hide */
+    @WindowConfiguration.WindowingMode
+    public int getWindowingMode() {
+        return configuration.windowConfiguration.getWindowingMode();
+    }
+
+    /** @hide */
+    @WindowConfiguration.ActivityType
+    public int getActivityType() {
+        return configuration.windowConfiguration.getActivityType();
+    }
+
+    /** @hide */
     public void addLaunchCookie(IBinder cookie) {
         if (cookie == null || launchCookies.contains(cookie)) return;
         launchCookies.add(cookie);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 224e3a8..0ade5d2 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -525,7 +525,7 @@
      * @hide
      */
     public static final String ACTION_REMOTE_BUGREPORT_DISPATCH =
-            "com.android.server.action.REMOTE_BUGREPORT_DISPATCH";
+            "android.intent.action.REMOTE_BUGREPORT_DISPATCH";
 
     /**
      * Extra for shared bugreport's SHA-256 hash.
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index 73becb1..e3395e2 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -74,8 +74,8 @@
 
     /**
      * The MIME type for a shortcut. The ClipData must include intents with required extras
-     * {@link #EXTRA_PENDING_INTENT} and {@link Intent#EXTRA_USER}, and an optional
-     * {@link #EXTRA_ACTIVITY_OPTIONS}.
+     * {@link Intent#EXTRA_SHORTCUT_ID}, {@link Intent#EXTRA_PACKAGE_NAME} and
+     * {@link Intent#EXTRA_USER}, and an optional {@link #EXTRA_ACTIVITY_OPTIONS}.
      * @hide
      */
     public static final String MIMETYPE_APPLICATION_SHORTCUT = "application/vnd.android.shortcut";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 782f0cd..a03bdf2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2740,6 +2740,7 @@
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_STARTABLE = "android.intent.action.PACKAGE_STARTABLE";
@@ -2757,6 +2758,7 @@
      * </ul>
      *
      * <p class="note">This is a protected intent that can only be sent by the system.
+     * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_PACKAGE_UNSTARTABLE =
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 946c634..a77d8b1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3824,18 +3824,21 @@
      * Unstartable state with no root cause specified. E.g., data loader seeing missing pages but
      * unclear about the cause. This corresponds to a generic alert window shown to the user when
      * the user attempts to launch the app.
+     * @hide
      */
     public static final int UNSTARTABLE_REASON_UNKNOWN = 0;
 
     /**
      * Unstartable state due to connection issues that interrupt package loading.
      * This corresponds to an alert window shown to the user indicating connection errors.
+     * @hide
      */
     public static final int UNSTARTABLE_REASON_CONNECTION_ERROR = 1;
 
     /**
      * Unstartable state after encountering storage limitations.
      * This corresponds to an alert window indicating limited storage.
+     * @hide
      */
     public static final int UNSTARTABLE_REASON_INSUFFICIENT_STORAGE = 2;
 
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 401bb9d..bf32560 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -42,9 +42,11 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 /**
  * The {@link HdmiControlManager} class is used to send HDMI control messages
@@ -325,17 +327,17 @@
      *
      * @hide
      */
-    public static final String HDMI_CEC_CONTROL_ENABLED = "1";
+    public static final int HDMI_CEC_CONTROL_ENABLED = 1;
     /**
      * HDMI CEC disabled.
      *
      * @hide
      */
-    public static final String HDMI_CEC_CONTROL_DISABLED = "0";
+    public static final int HDMI_CEC_CONTROL_DISABLED = 0;
     /**
      * @hide
      */
-    @StringDef({
+    @IntDef({
             HDMI_CEC_CONTROL_ENABLED,
             HDMI_CEC_CONTROL_DISABLED
     })
@@ -401,17 +403,17 @@
      *
      * @hide
      */
-    public static final String SYSTEM_AUDIO_MODE_MUTING_ENABLED = "1";
+    public static final int SYSTEM_AUDIO_MODE_MUTING_ENABLED = 1;
     /**
      * System Audio Mode muting disabled.
      *
      * @hide
      */
-    public static final String SYSTEM_AUDIO_MODE_MUTING_DISABLED = "0";
+    public static final int SYSTEM_AUDIO_MODE_MUTING_DISABLED = 0;
     /**
      * @hide
      */
-    @StringDef({
+    @IntDef({
             SYSTEM_AUDIO_MODE_MUTING_ENABLED,
             SYSTEM_AUDIO_MODE_MUTING_DISABLED
     })
@@ -1317,24 +1319,51 @@
     }
 
     /**
-     * Get a set of allowed values for a settings.
+     * Get a set of allowed values for a setting (string value-type).
      *
      * @param name name of the setting
      * @return a set of allowed values for a settings. {@code null} on failure.
      * @throws IllegalArgumentException when setting {@code name} does not exist.
+     * @throws IllegalArgumentException when setting {@code name} value type is invalid.
      * @throws RuntimeException when the HdmiControlService is not available.
      *
      * @hide
      */
     @NonNull
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
-    public List<String> getAllowedCecSettingValues(@NonNull @CecSettingName String name) {
+    public List<String> getAllowedCecSettingStringValues(@NonNull @CecSettingName String name) {
         if (mService == null) {
             Log.e(TAG, "HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            return mService.getAllowedCecSettingValues(name);
+            return mService.getAllowedCecSettingStringValues(name);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get a set of allowed values for a setting (int value-type).
+     *
+     * @param name name of the setting
+     * @return a set of allowed values for a settings. {@code null} on failure.
+     * @throws IllegalArgumentException when setting {@code name} does not exist.
+     * @throws IllegalArgumentException when setting {@code name} value type is invalid.
+     * @throws RuntimeException when the HdmiControlService is not available.
+     *
+     * @hide
+     */
+    @NonNull
+    @RequiresPermission(android.Manifest.permission.HDMI_CEC)
+    public List<Integer> getAllowedCecSettingIntValues(@NonNull @CecSettingName String name) {
+        if (mService == null) {
+            Log.e(TAG, "HdmiControlService is not available");
+            throw new RuntimeException("HdmiControlService is not available");
+        }
+        try {
+            int[] allowedValues = mService.getAllowedCecSettingIntValues(name);
+            return Arrays.stream(allowedValues).boxed().collect(Collectors.toList());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1350,13 +1379,13 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
-    public void setHdmiCecEnabled(@NonNull @HdmiCecControl String value) {
+    public void setHdmiCecEnabled(@NonNull @HdmiCecControl int value) {
         if (mService == null) {
             Log.e(TAG, "HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            mService.setCecSettingValue(CEC_SETTING_NAME_HDMI_CEC_ENABLED, value);
+            mService.setCecSettingIntValue(CEC_SETTING_NAME_HDMI_CEC_ENABLED, value);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1373,13 +1402,13 @@
     @NonNull
     @HdmiCecControl
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
-    public String getHdmiCecEnabled() {
+    public int getHdmiCecEnabled() {
         if (mService == null) {
             Log.e(TAG, "HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            return mService.getCecSettingValue(CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+            return mService.getCecSettingIntValue(CEC_SETTING_NAME_HDMI_CEC_ENABLED);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1401,7 +1430,7 @@
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            mService.setCecSettingValue(CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP, value);
+            mService.setCecSettingStringValue(CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP, value);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1424,7 +1453,7 @@
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            return mService.getCecSettingValue(CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP);
+            return mService.getCecSettingStringValue(CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1447,7 +1476,7 @@
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            mService.setCecSettingValue(
+            mService.setCecSettingStringValue(
                     CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, value);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1471,7 +1500,7 @@
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            return mService.getCecSettingValue(
+            return mService.getCecSettingStringValue(
                     CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1488,13 +1517,13 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
-    public void setSystemAudioModeMuting(@NonNull @SystemAudioModeMuting String value) {
+    public void setSystemAudioModeMuting(@NonNull @SystemAudioModeMuting int value) {
         if (mService == null) {
             Log.e(TAG, "HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            mService.setCecSettingValue(CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, value);
+            mService.setCecSettingIntValue(CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, value);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1511,13 +1540,13 @@
     @NonNull
     @SystemAudioModeMuting
     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
-    public String getSystemAudioModeMuting() {
+    public int getSystemAudioModeMuting() {
         if (mService == null) {
             Log.e(TAG, "HdmiControlService is not available");
             throw new RuntimeException("HdmiControlService is not available");
         }
         try {
-            return mService.getCecSettingValue(CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING);
+            return mService.getCecSettingIntValue(CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
index 22d4640..fab56b8 100644
--- a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
+++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java
@@ -301,18 +301,33 @@
         }
 
         @Override
-        public List<String> getAllowedCecSettingValues(String name) {
-            return HdmiControlServiceWrapper.this.getAllowedCecSettingValues(name);
+        public List<String> getAllowedCecSettingStringValues(String name) {
+            return HdmiControlServiceWrapper.this.getAllowedCecSettingStringValues(name);
         }
 
         @Override
-        public String getCecSettingValue(String name) {
-            return HdmiControlServiceWrapper.this.getCecSettingValue(name);
+        public int[] getAllowedCecSettingIntValues(String name) {
+            return HdmiControlServiceWrapper.this.getAllowedCecSettingIntValues(name);
         }
 
         @Override
-        public void setCecSettingValue(String name, String value) {
-            HdmiControlServiceWrapper.this.setCecSettingValue(name, value);
+        public String getCecSettingStringValue(String name) {
+            return HdmiControlServiceWrapper.this.getCecSettingStringValue(name);
+        }
+
+        @Override
+        public void setCecSettingStringValue(String name, String value) {
+            HdmiControlServiceWrapper.this.setCecSettingStringValue(name, value);
+        }
+
+        @Override
+        public int getCecSettingIntValue(String name) {
+            return HdmiControlServiceWrapper.this.getCecSettingIntValue(name);
+        }
+
+        @Override
+        public void setCecSettingIntValue(String name, int value) {
+            HdmiControlServiceWrapper.this.setCecSettingIntValue(name, value);
         }
     };
 
@@ -494,15 +509,30 @@
     }
 
     /** @hide */
-    public List<String> getAllowedCecSettingValues(String name) {
+    public List<String> getAllowedCecSettingStringValues(String name) {
         return new ArrayList<>();
     }
 
     /** @hide */
-    public String getCecSettingValue(String name) {
+    public int[] getAllowedCecSettingIntValues(String name) {
+        return new int[0];
+    }
+
+    /** @hide */
+    public String getCecSettingStringValue(String name) {
         return "";
     }
 
     /** @hide */
-    public void setCecSettingValue(String name, String value) {}
+    public void setCecSettingStringValue(String name, String value) {
+    }
+
+    /** @hide */
+    public int getCecSettingIntValue(String name) {
+        return 0;
+    }
+
+    /** @hide */
+    public void setCecSettingIntValue(String name, int value) {
+    }
 }
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index 6df164b..af9d3ac 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -88,7 +88,10 @@
     void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute);
     void setSystemAudioModeOnForAudioOnlySource();
     List<String> getUserCecSettings();
-    List<String> getAllowedCecSettingValues(String name);
-    String getCecSettingValue(String name);
-    void setCecSettingValue(String name, String value);
+    List<String> getAllowedCecSettingStringValues(String name);
+    int[] getAllowedCecSettingIntValues(String name);
+    String getCecSettingStringValue(String name);
+    void setCecSettingStringValue(String name, String value);
+    int getCecSettingIntValue(String name);
+    void setCecSettingIntValue(String name, int value);
 }
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 32a8c0a..44640c4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -46,6 +46,7 @@
 import static android.inputmethodservice.InputMethodServiceProto.TOKEN;
 import static android.inputmethodservice.InputMethodServiceProto.VIEWS_CREATED;
 import static android.inputmethodservice.InputMethodServiceProto.WINDOW_VISIBLE;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowInsets.Type.navigationBars;
@@ -80,6 +81,7 @@
 import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.provider.Settings;
 import android.text.InputType;
 import android.text.Layout;
@@ -645,7 +647,9 @@
         @Override
         public void startInput(InputConnection ic, EditorInfo attribute) {
             if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.startInput");
             doStartInput(ic, attribute, false);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         /**
@@ -705,6 +709,8 @@
                 return;
             }
             final boolean wasVisible = isInputViewShown();
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput");
+
             applyVisibilityInInsetsConsumerIfNecessary(false /* setVisible */);
             mShowInputFlags = 0;
             mShowInputRequested = false;
@@ -717,6 +723,7 @@
                         : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         /**
@@ -748,6 +755,13 @@
                         + " Use requestShowSelf(int) itself");
                 return;
             }
+
+            if (Trace.isEnabled()) {
+                Binder.enableTracing();
+            } else {
+                Binder.disableTracing();
+            }
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput");
             final boolean wasVisible = isInputViewShown();
             if (dispatchOnShowInputRequested(flags, false)) {
 
@@ -764,6 +778,7 @@
                         : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
                                 : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         /**
diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java
index 77bfa57..f552aaa 100644
--- a/core/java/android/os/CombinedVibrationEffect.java
+++ b/core/java/android/os/CombinedVibrationEffect.java
@@ -17,7 +17,12 @@
 package android.os;
 
 import android.annotation.NonNull;
+import android.util.SparseArray;
 
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -31,6 +36,8 @@
  */
 public abstract class CombinedVibrationEffect implements Parcelable {
     private static final int PARCEL_TOKEN_MONO = 1;
+    private static final int PARCEL_TOKEN_STEREO = 2;
+    private static final int PARCEL_TOKEN_SEQUENTIAL = 3;
 
     /** @hide to prevent subclassing from outside of the framework */
     public CombinedVibrationEffect() {
@@ -41,8 +48,8 @@
      *
      * A synced vibration effect should be performed by multiple vibrators at the same time.
      *
-     * @param effect The {@link VibrationEffect} to perform
-     * @return The desired combined effect.
+     * @param effect The {@link VibrationEffect} to perform.
+     * @return The synced effect.
      */
     @NonNull
     public static CombinedVibrationEffect createSynced(@NonNull VibrationEffect effect) {
@@ -51,6 +58,30 @@
         return combined;
     }
 
+    /**
+     * Start creating a synced vibration effect.
+     *
+     * A synced vibration effect should be performed by multiple vibrators at the same time.
+     *
+     * @see CombinedVibrationEffect.SyncedCombination
+     */
+    @NonNull
+    public static SyncedCombination startSynced() {
+        return new SyncedCombination();
+    }
+
+    /**
+     * Start creating a sequential vibration effect.
+     *
+     * A sequential vibration effect should be performed by multiple vibrators in order.
+     *
+     * @see CombinedVibrationEffect.SequentialCombination
+     */
+    @NonNull
+    public static SequentialCombination startSequential() {
+        return new SequentialCombination();
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -60,6 +91,164 @@
     public abstract void validate();
 
     /**
+     * A combination of haptic effects that should be played in multiple vibrators in sync.
+     *
+     * @hide
+     * @see CombinedVibrationEffect#startSynced()
+     */
+    public static final class SyncedCombination {
+
+        private final SparseArray<VibrationEffect> mEffects = new SparseArray<>();
+
+        SyncedCombination() {
+        }
+
+        /**
+         * Add or replace a one shot vibration effect to be performed by the specified vibrator.
+         *
+         * @param vibratorId The id of the vibrator that should perform this effect.
+         * @param effect     The effect this vibrator should play.
+         * @return The {@link CombinedVibrationEffect.SyncedCombination} object to enable adding
+         * multiple effects in one chain.
+         * @see VibrationEffect#createOneShot(long, int)
+         */
+        @NonNull
+        public SyncedCombination addVibrator(int vibratorId, VibrationEffect effect) {
+            mEffects.put(vibratorId, effect);
+            return this;
+        }
+
+        /**
+         * Combine all of the added effects into a combined effect.
+         *
+         * The {@link CombinedVibrationEffect.SyncedCombination} object is still valid after this
+         * call, so you can continue adding more effects to it and generating more
+         * {@link CombinedVibrationEffect}s by calling this method again.
+         *
+         * @return The {@link CombinedVibrationEffect} resulting from combining the added effects to
+         * be played in sync.
+         */
+        @NonNull
+        public CombinedVibrationEffect combine() {
+            if (mEffects.size() == 0) {
+                throw new IllegalStateException(
+                        "Combination must have at least one element to combine.");
+            }
+            CombinedVibrationEffect combined = new Stereo(mEffects);
+            combined.validate();
+            return combined;
+        }
+    }
+
+    /**
+     * A combination of haptic effects that should be played in multiple vibrators in sequence.
+     *
+     * @hide
+     * @see CombinedVibrationEffect#startSequential()
+     */
+    public static final class SequentialCombination {
+
+        private final ArrayList<CombinedVibrationEffect> mEffects = new ArrayList<>();
+        private final ArrayList<Integer> mDelays = new ArrayList<>();
+
+        SequentialCombination() {
+        }
+
+        /**
+         * Add a single vibration effect to be performed next.
+         *
+         * Similar to {@link #addNext(int, VibrationEffect, int)}, but with no delay.
+         *
+         * @param vibratorId The id of the vibrator that should perform this effect.
+         * @param effect     The effect this vibrator should play.
+         * @return The {@link CombinedVibrationEffect.SequentialCombination} object to enable adding
+         * multiple effects in one chain.
+         */
+        @NonNull
+        public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect) {
+            return addNext(vibratorId, effect, /* delay= */ 0);
+        }
+
+        /**
+         * Add a single vibration effect to be performed next.
+         *
+         * @param vibratorId The id of the vibrator that should perform this effect.
+         * @param effect     The effect this vibrator should play.
+         * @param delay      The amount of time, in milliseconds, to wait between playing the prior
+         *                   effect and this one.
+         * @return The {@link CombinedVibrationEffect.SequentialCombination} object to enable adding
+         * multiple effects in one chain.
+         */
+        @NonNull
+        public SequentialCombination addNext(int vibratorId, @NonNull VibrationEffect effect,
+                int delay) {
+            return addNext(
+                    CombinedVibrationEffect.startSynced().addVibrator(vibratorId, effect).combine(),
+                    delay);
+        }
+
+        /**
+         * Add a combined vibration effect to be performed next.
+         *
+         * Similar to {@link #addNext(CombinedVibrationEffect, int)}, but with no delay.
+         *
+         * @param effect The combined effect to be performed next.
+         * @return The {@link CombinedVibrationEffect.SequentialCombination} object to enable adding
+         * multiple effects in one chain.
+         * @see VibrationEffect#createOneShot(long, int)
+         */
+        @NonNull
+        public SequentialCombination addNext(@NonNull CombinedVibrationEffect effect) {
+            return addNext(effect, /* delay= */ 0);
+        }
+
+        /**
+         * Add a one shot vibration effect to be performed by the specified vibrator.
+         *
+         * @param effect The combined effect to be performed next.
+         * @param delay  The amount of time, in milliseconds, to wait between playing the prior
+         *               effect and this one.
+         * @return The {@link CombinedVibrationEffect.SequentialCombination} object to enable adding
+         * multiple effects in one chain.
+         */
+        @NonNull
+        public SequentialCombination addNext(@NonNull CombinedVibrationEffect effect, int delay) {
+            if (effect instanceof Sequential) {
+                Sequential sequentialEffect = (Sequential) effect;
+                int firstEffectIndex = mDelays.size();
+                mEffects.addAll(sequentialEffect.getEffects());
+                mDelays.addAll(sequentialEffect.getDelays());
+                mDelays.set(firstEffectIndex, delay + mDelays.get(firstEffectIndex));
+            } else {
+                mEffects.add(effect);
+                mDelays.add(delay);
+            }
+            return this;
+        }
+
+        /**
+         * Combine all of the added effects in sequence.
+         *
+         * The {@link CombinedVibrationEffect.SequentialCombination} object is still valid after
+         * this call, so you can continue adding more effects to it and generating more {@link
+         * CombinedVibrationEffect}s by calling this method again.
+         *
+         * @return The {@link CombinedVibrationEffect} resulting from combining the added effects to
+         * be played in sequence.
+         */
+        @NonNull
+        public CombinedVibrationEffect combine() {
+            if (mEffects.size() == 0) {
+                throw new IllegalStateException(
+                        "Combination must have at least one element to combine.");
+            }
+            CombinedVibrationEffect combined = new Sequential(mEffects, mDelays);
+            combined.validate();
+            return combined;
+        }
+    }
+
+    /**
      * Represents a single {@link VibrationEffect} that should be executed in all vibrators in sync.
      *
      * @hide
@@ -87,10 +276,10 @@
 
         @Override
         public boolean equals(Object o) {
-            if (!(o instanceof CombinedVibrationEffect.Mono)) {
+            if (!(o instanceof Mono)) {
                 return false;
             }
-            CombinedVibrationEffect.Mono other = (CombinedVibrationEffect.Mono) o;
+            Mono other = (Mono) o;
             return other.mEffect.equals(other.mEffect);
         }
 
@@ -128,6 +317,206 @@
                 };
     }
 
+    /**
+     * Represents a list of {@link VibrationEffect}s that should be executed in sync.
+     *
+     * @hide
+     */
+    public static final class Stereo extends CombinedVibrationEffect {
+
+        /** Mapping vibrator ids to effects. */
+        private final SparseArray<VibrationEffect> mEffects;
+
+        public Stereo(Parcel in) {
+            int size = in.readInt();
+            mEffects = new SparseArray<>(size);
+            for (int i = 0; i < size; i++) {
+                int vibratorId = in.readInt();
+                mEffects.put(vibratorId, VibrationEffect.CREATOR.createFromParcel(in));
+            }
+        }
+
+        public Stereo(@NonNull SparseArray<VibrationEffect> effects) {
+            mEffects = new SparseArray<>(effects.size());
+            for (int i = 0; i < effects.size(); i++) {
+                mEffects.put(effects.keyAt(i), effects.valueAt(i));
+            }
+        }
+
+        /** Effects to be performed in sync, where each key represents the vibrator id. */
+        public SparseArray<VibrationEffect> getEffects() {
+            return mEffects;
+        }
+
+        /** @hide */
+        @Override
+        public void validate() {
+            Preconditions.checkArgument(mEffects.size() > 0,
+                    "There should be at least one effect set for a combined effect");
+            for (int i = 0; i < mEffects.size(); i++) {
+                mEffects.valueAt(i).validate();
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Stereo)) {
+                return false;
+            }
+            Stereo other = (Stereo) o;
+            if (mEffects.size() != other.mEffects.size()) {
+                return false;
+            }
+            for (int i = 0; i < mEffects.size(); i++) {
+                if (!mEffects.valueAt(i).equals(other.mEffects.get(mEffects.keyAt(i)))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mEffects);
+        }
+
+        @Override
+        public String toString() {
+            return "Stereo{mEffects=" + mEffects + '}';
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_STEREO);
+            out.writeInt(mEffects.size());
+            for (int i = 0; i < mEffects.size(); i++) {
+                out.writeInt(mEffects.keyAt(i));
+                mEffects.valueAt(i).writeToParcel(out, flags);
+            }
+        }
+
+        @NonNull
+        public static final Parcelable.Creator<Stereo> CREATOR =
+                new Parcelable.Creator<Stereo>() {
+                    @Override
+                    public Stereo createFromParcel(@NonNull Parcel in) {
+                        // Skip the type token
+                        in.readInt();
+                        return new Stereo(in);
+                    }
+
+                    @Override
+                    @NonNull
+                    public Stereo[] newArray(int size) {
+                        return new Stereo[size];
+                    }
+                };
+    }
+
+    /**
+     * Represents a list of {@link VibrationEffect}s that should be executed in sequence.
+     *
+     * @hide
+     */
+    public static final class Sequential extends CombinedVibrationEffect {
+        private final List<CombinedVibrationEffect> mEffects;
+        private final List<Integer> mDelays;
+
+        public Sequential(Parcel in) {
+            int size = in.readInt();
+            mEffects = new ArrayList<>(size);
+            mDelays = new ArrayList<>(size);
+            for (int i = 0; i < size; i++) {
+                mDelays.add(in.readInt());
+                mEffects.add(CombinedVibrationEffect.CREATOR.createFromParcel(in));
+            }
+        }
+
+        public Sequential(@NonNull List<CombinedVibrationEffect> effects,
+                @NonNull List<Integer> delays) {
+            mEffects = new ArrayList<>(effects);
+            mDelays = new ArrayList<>(delays);
+        }
+
+        /** Effects to be performed in sequence. */
+        public List<CombinedVibrationEffect> getEffects() {
+            return mEffects;
+        }
+
+        /** Delay to be applied before each effect in {@link #getEffects()}. */
+        public List<Integer> getDelays() {
+            return mDelays;
+        }
+
+        /** @hide */
+        @Override
+        public void validate() {
+            Preconditions.checkArgument(mEffects.size() > 0,
+                    "There should be at least one effect set for a combined effect");
+            Preconditions.checkArgument(mEffects.size() == mDelays.size(),
+                    "Effect and delays should have equal length");
+            for (long delay : mDelays) {
+                if (delay < 0) {
+                    throw new IllegalArgumentException("Delays must all be >= 0"
+                            + " (delays=" + mDelays + ")");
+                }
+            }
+            for (CombinedVibrationEffect effect : mEffects) {
+                if (effect instanceof Sequential) {
+                    throw new IllegalArgumentException(
+                            "There should be no nested sequential effects in a combined effect");
+                }
+                effect.validate();
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof Sequential)) {
+                return false;
+            }
+            Sequential other = (Sequential) o;
+            return mDelays.equals(other.mDelays) && mEffects.equals(other.mEffects);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mEffects);
+        }
+
+        @Override
+        public String toString() {
+            return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}';
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(PARCEL_TOKEN_SEQUENTIAL);
+            out.writeInt(mEffects.size());
+            for (int i = 0; i < mEffects.size(); i++) {
+                out.writeInt(mDelays.get(i));
+                mEffects.get(i).writeToParcel(out, flags);
+            }
+        }
+
+        @NonNull
+        public static final Parcelable.Creator<Sequential> CREATOR =
+                new Parcelable.Creator<Sequential>() {
+                    @Override
+                    public Sequential createFromParcel(@NonNull Parcel in) {
+                        // Skip the type token
+                        in.readInt();
+                        return new Sequential(in);
+                    }
+
+                    @Override
+                    @NonNull
+                    public Sequential[] newArray(int size) {
+                        return new Sequential[size];
+                    }
+                };
+    }
+
     @NonNull
     public static final Parcelable.Creator<CombinedVibrationEffect> CREATOR =
             new Parcelable.Creator<CombinedVibrationEffect>() {
@@ -135,7 +524,11 @@
                 public CombinedVibrationEffect createFromParcel(Parcel in) {
                     int token = in.readInt();
                     if (token == PARCEL_TOKEN_MONO) {
-                        return new CombinedVibrationEffect.Mono(in);
+                        return new Mono(in);
+                    } else if (token == PARCEL_TOKEN_STEREO) {
+                        return new Stereo(in);
+                    } else if (token == PARCEL_TOKEN_SEQUENTIAL) {
+                        return new Sequential(in);
                     } else {
                         throw new IllegalStateException(
                                 "Unexpected combined vibration event type token in parcel.");
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2edd322..aba1f28 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4033,7 +4033,8 @@
      * @return the {@link RemoveResult} code
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
+            Manifest.permission.CREATE_USERS})
     public @RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId) {
         try {
             return mService.removeUserOrSetEphemeral(userId);
diff --git a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
index 2a809b1..4ffffc6 100644
--- a/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
+++ b/core/java/android/service/carrier/CarrierMessagingServiceWrapper.java
@@ -46,12 +46,13 @@
  * CarrierMessagingService.
  * @hide
  */
-public abstract class CarrierMessagingServiceWrapper {
+public final class CarrierMessagingServiceWrapper {
     // Populated by bindToCarrierMessagingService. bindToCarrierMessagingService must complete
     // prior to calling disposeConnection so that mCarrierMessagingServiceConnection is initialized.
     private volatile CarrierMessagingServiceConnection mCarrierMessagingServiceConnection;
 
     private volatile ICarrierMessagingService mICarrierMessagingService;
+    private Runnable mOnServiceReadyCallback;
 
     /**
      * Binds to the carrier messaging service under package {@code carrierPackageName}. This method
@@ -63,12 +64,14 @@
      * @hide
      */
     public boolean bindToCarrierMessagingService(@NonNull Context context,
-            @NonNull String carrierPackageName) {
+            @NonNull String carrierPackageName,
+            @NonNull Runnable onServiceReadyCallback) {
         Preconditions.checkState(mCarrierMessagingServiceConnection == null);
 
         Intent intent = new Intent(CarrierMessagingService.SERVICE_INTERFACE);
         intent.setPackage(carrierPackageName);
         mCarrierMessagingServiceConnection = new CarrierMessagingServiceConnection();
+        mOnServiceReadyCallback = onServiceReadyCallback;
         return context.bindService(intent, mCarrierMessagingServiceConnection,
                 Context.BIND_AUTO_CREATE);
     }
@@ -83,22 +86,17 @@
         Preconditions.checkNotNull(mCarrierMessagingServiceConnection);
         context.unbindService(mCarrierMessagingServiceConnection);
         mCarrierMessagingServiceConnection = null;
+        mOnServiceReadyCallback = null;
     }
 
     /**
-     * Implemented by subclasses to use the carrier messaging service once it is ready.
-     * @hide
-     */
-    public abstract void onServiceReady();
-
-    /**
      * Called when connection with service is established.
      *
      * @param carrierMessagingService the carrier messaing service interface
      */
     private void onServiceReady(ICarrierMessagingService carrierMessagingService) {
         mICarrierMessagingService = carrierMessagingService;
-        onServiceReady();
+        if (mOnServiceReadyCallback != null) mOnServiceReadyCallback.run();
     }
 
     /**
@@ -113,11 +111,11 @@
      * @hide
      */
     public void filterSms(@NonNull MessagePdu pdu, @NonNull String format, int destPort,
-            int subId, @NonNull final CarrierMessagingCallbackWrapper callback) {
+            int subId, @NonNull final CarrierMessagingCallback callback) {
         if (mICarrierMessagingService != null) {
             try {
                 mICarrierMessagingService.filterSms(pdu, format, destPort, subId,
-                        new CarrierMessagingCallbackWrapperInternal(callback));
+                        new CarrierMessagingCallbackInternal(callback));
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -137,11 +135,11 @@
      * @hide
      */
     public void sendTextSms(@NonNull String text, int subId, @NonNull String destAddress,
-            int sendSmsFlag, @NonNull final CarrierMessagingCallbackWrapper callback) {
+            int sendSmsFlag, @NonNull final CarrierMessagingCallback callback) {
         if (mICarrierMessagingService != null) {
             try {
                 mICarrierMessagingService.sendTextSms(text, subId, destAddress, sendSmsFlag,
-                        new CarrierMessagingCallbackWrapperInternal(callback));
+                        new CarrierMessagingCallbackInternal(callback));
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -163,11 +161,11 @@
      */
     public void sendDataSms(@NonNull byte[] data, int subId, @NonNull String destAddress,
             int destPort, int sendSmsFlag,
-            @NonNull final CarrierMessagingCallbackWrapper callback) {
+            @NonNull final CarrierMessagingCallback callback) {
         if (mICarrierMessagingService != null) {
             try {
                 mICarrierMessagingService.sendDataSms(data, subId, destAddress, destPort,
-                        sendSmsFlag, new CarrierMessagingCallbackWrapperInternal(callback));
+                        sendSmsFlag, new CarrierMessagingCallbackInternal(callback));
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -188,11 +186,11 @@
      */
     public void sendMultipartTextSms(@NonNull List<String> parts, int subId,
             @NonNull String destAddress, int sendSmsFlag,
-            @NonNull final CarrierMessagingCallbackWrapper callback) {
+            @NonNull final CarrierMessagingCallback callback) {
         if (mICarrierMessagingService != null) {
             try {
                 mICarrierMessagingService.sendMultipartTextSms(parts, subId, destAddress,
-                        sendSmsFlag, new CarrierMessagingCallbackWrapperInternal(callback));
+                        sendSmsFlag, new CarrierMessagingCallbackInternal(callback));
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -212,11 +210,11 @@
      * @hide
      */
     public void sendMms(@NonNull Uri pduUri, int subId, @NonNull Uri location,
-            @NonNull final CarrierMessagingCallbackWrapper callback) {
+            @NonNull final CarrierMessagingCallback callback) {
         if (mICarrierMessagingService != null) {
             try {
                 mICarrierMessagingService.sendMms(pduUri, subId, location,
-                        new CarrierMessagingCallbackWrapperInternal(callback));
+                        new CarrierMessagingCallbackInternal(callback));
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -235,11 +233,11 @@
      * @hide
      */
     public void downloadMms(@NonNull Uri pduUri, int subId, @NonNull Uri location,
-            @NonNull final CarrierMessagingCallbackWrapper callback) {
+            @NonNull final CarrierMessagingCallback callback) {
         if (mICarrierMessagingService != null) {
             try {
                 mICarrierMessagingService.downloadMms(pduUri, subId, location,
-                        new CarrierMessagingCallbackWrapperInternal(callback));
+                        new CarrierMessagingCallbackInternal(callback));
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
@@ -265,7 +263,7 @@
      * {@link CarrierMessagingServiceWrapper}.
      * @hide
      */
-    public abstract static class CarrierMessagingCallbackWrapper {
+    public interface CarrierMessagingCallback {
 
         /**
          * Response callback for {@link CarrierMessagingServiceWrapper#filterSms}.
@@ -277,7 +275,7 @@
          *               {@see CarrierMessagingService#onReceiveTextSms}.
          * @hide
          */
-        public void onFilterComplete(int result) {
+        default void onFilterComplete(int result) {
 
         }
 
@@ -291,7 +289,7 @@
          *                   only if result is {@link CarrierMessagingService#SEND_STATUS_OK}.
          * @hide
          */
-        public void onSendSmsComplete(int result, int messageRef) {
+        default void onSendSmsComplete(int result, int messageRef) {
 
         }
 
@@ -305,7 +303,7 @@
          *                    {@link CarrierMessagingService#SEND_STATUS_OK}.
          * @hide
          */
-        public void onSendMultipartSmsComplete(int result, @Nullable int[] messageRefs) {
+        default void onSendMultipartSmsComplete(int result, @Nullable int[] messageRefs) {
 
         }
 
@@ -319,7 +317,7 @@
          *                    {@link CarrierMessagingService#SEND_STATUS_OK}.
          * @hide
          */
-        public void onSendMmsComplete(int result, @Nullable byte[] sendConfPdu) {
+        default void onSendMmsComplete(int result, @Nullable byte[] sendConfPdu) {
 
         }
 
@@ -330,43 +328,43 @@
          *               and {@link CarrierMessagingService#SEND_STATUS_ERROR}.
          * @hide
          */
-        public void onDownloadMmsComplete(int result) {
+        default void onDownloadMmsComplete(int result) {
 
         }
     }
 
-    private final class CarrierMessagingCallbackWrapperInternal
+    private final class CarrierMessagingCallbackInternal
             extends ICarrierMessagingCallback.Stub {
-        CarrierMessagingCallbackWrapper mCarrierMessagingCallbackWrapper;
+        CarrierMessagingCallback mCarrierMessagingCallback;
 
-        CarrierMessagingCallbackWrapperInternal(CarrierMessagingCallbackWrapper callback) {
-            mCarrierMessagingCallbackWrapper = callback;
+        CarrierMessagingCallbackInternal(CarrierMessagingCallback callback) {
+            mCarrierMessagingCallback = callback;
         }
 
         @Override
         public void onFilterComplete(int result) throws RemoteException {
-            mCarrierMessagingCallbackWrapper.onFilterComplete(result);
+            mCarrierMessagingCallback.onFilterComplete(result);
         }
 
         @Override
         public void onSendSmsComplete(int result, int messageRef) throws RemoteException {
-            mCarrierMessagingCallbackWrapper.onSendSmsComplete(result, messageRef);
+            mCarrierMessagingCallback.onSendSmsComplete(result, messageRef);
         }
 
         @Override
         public void onSendMultipartSmsComplete(int result, int[] messageRefs)
                 throws RemoteException {
-            mCarrierMessagingCallbackWrapper.onSendMultipartSmsComplete(result, messageRefs);
+            mCarrierMessagingCallback.onSendMultipartSmsComplete(result, messageRefs);
         }
 
         @Override
         public void onSendMmsComplete(int result, byte[] sendConfPdu) throws RemoteException {
-            mCarrierMessagingCallbackWrapper.onSendMmsComplete(result, sendConfPdu);
+            mCarrierMessagingCallback.onSendMmsComplete(result, sendConfPdu);
         }
 
         @Override
         public void onDownloadMmsComplete(int result) throws RemoteException {
-            mCarrierMessagingCallbackWrapper.onDownloadMmsComplete(result);
+            mCarrierMessagingCallback.onDownloadMmsComplete(result);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/core/java/android/timezone/TzDataSetVersion.java b/core/java/android/timezone/TzDataSetVersion.java
index edcbbb3..4031ff8 100644
--- a/core/java/android/timezone/TzDataSetVersion.java
+++ b/core/java/android/timezone/TzDataSetVersion.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.i18n.timezone.TimeZoneDataFiles;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.IOException;
@@ -76,8 +77,7 @@
     @NonNull
     public static TzDataSetVersion read() throws IOException, TzDataSetException {
         try {
-            return new TzDataSetVersion(
-                    com.android.i18n.timezone.TzDataSetVersion.readTimeZoneModuleVersion());
+            return new TzDataSetVersion(TimeZoneDataFiles.readTimeZoneModuleVersion());
         } catch (com.android.i18n.timezone.TzDataSetVersion.TzDataSetException e) {
             throw new TzDataSetException(e.getMessage(), e);
         }
diff --git a/core/java/android/uwb/AngleMeasurement.java b/core/java/android/uwb/AngleMeasurement.java
index cf32b92..33bc121 100644
--- a/core/java/android/uwb/AngleMeasurement.java
+++ b/core/java/android/uwb/AngleMeasurement.java
@@ -20,6 +20,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Angle measurement
  *
@@ -75,6 +77,32 @@
         return mConfidenceLevel;
     }
 
+    /**
+     * @hide
+    */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof AngleMeasurement) {
+            AngleMeasurement other = (AngleMeasurement) obj;
+            return mRadians == other.getRadians()
+                    && mErrorRadians == other.getErrorRadians()
+                    && mConfidenceLevel == other.getConfidenceLevel();
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRadians, mErrorRadians, mConfidenceLevel);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/uwb/AngleOfArrivalMeasurement.java b/core/java/android/uwb/AngleOfArrivalMeasurement.java
index 646bd42..cd5af69 100644
--- a/core/java/android/uwb/AngleOfArrivalMeasurement.java
+++ b/core/java/android/uwb/AngleOfArrivalMeasurement.java
@@ -21,6 +21,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Represents an angle of arrival measurement between two devices using Ultra Wideband
  *
@@ -72,6 +74,31 @@
         return mAltitudeAngleMeasurement;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof AngleOfArrivalMeasurement) {
+            AngleOfArrivalMeasurement other = (AngleOfArrivalMeasurement) obj;
+            return mAzimuthAngleMeasurement.equals(other.getAzimuth())
+                    && mAltitudeAngleMeasurement.equals(other.getAltitude());
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAzimuthAngleMeasurement, mAltitudeAngleMeasurement);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/uwb/DistanceMeasurement.java b/core/java/android/uwb/DistanceMeasurement.java
index 9561be4..c959840 100644
--- a/core/java/android/uwb/DistanceMeasurement.java
+++ b/core/java/android/uwb/DistanceMeasurement.java
@@ -17,9 +17,12 @@
 package android.uwb;
 
 import android.annotation.FloatRange;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * A data point for the distance measurement
  *
@@ -71,6 +74,32 @@
         return mConfidenceLevel;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof DistanceMeasurement) {
+            DistanceMeasurement other = (DistanceMeasurement) obj;
+            return mMeters == other.getMeters()
+                    && mErrorMeters == other.getErrorMeters()
+                    && mConfidenceLevel == other.getConfidenceLevel();
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mMeters, mErrorMeters, mConfidenceLevel);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/uwb/RangingMeasurement.java b/core/java/android/uwb/RangingMeasurement.java
index d4d7fb2..f1c3162 100644
--- a/core/java/android/uwb/RangingMeasurement.java
+++ b/core/java/android/uwb/RangingMeasurement.java
@@ -26,6 +26,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * Representation of a ranging measurement between the local device and a remote device
@@ -129,6 +130,35 @@
         return mAngleOfArrivalMeasurement;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof RangingMeasurement) {
+            RangingMeasurement other = (RangingMeasurement) obj;
+            return mRemoteDeviceAddress.equals(other.getRemoteDeviceAddress())
+                    && mStatus == other.getStatus()
+                    && mElapsedRealtimeNanos == other.getElapsedRealtimeNanos()
+                    && mDistanceMeasurement.equals(other.getDistance())
+                    && mAngleOfArrivalMeasurement.equals(other.getAngleOfArrival());
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRemoteDeviceAddress, mStatus, mElapsedRealtimeNanos,
+                mDistanceMeasurement, mAngleOfArrivalMeasurement);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/uwb/RangingParams.java b/core/java/android/uwb/RangingParams.java
index c5d4807..f23d9ed 100644
--- a/core/java/android/uwb/RangingParams.java
+++ b/core/java/android/uwb/RangingParams.java
@@ -30,6 +30,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -201,6 +202,43 @@
         return new PersistableBundle(mSpecificationParameters);
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof RangingParams) {
+            RangingParams other = (RangingParams) obj;
+
+            return mIsInitiator == other.mIsInitiator
+                    && mIsController == other.mIsController
+                    && mSamplePeriod.equals(other.mSamplePeriod)
+                    && mLocalDeviceAddress.equals(other.mLocalDeviceAddress)
+                    && mRemoteDeviceAddresses.equals(other.mRemoteDeviceAddresses)
+                    && mChannelNumber == other.mChannelNumber
+                    && mTransmitPreambleCodeIndex == other.mTransmitPreambleCodeIndex
+                    && mReceivePreambleCodeIndex == other.mReceivePreambleCodeIndex
+                    && mStsPhyPacketType == other.mStsPhyPacketType
+                    && mSpecificationParameters.size() == other.mSpecificationParameters.size()
+                    && mSpecificationParameters.kindofEquals(other.mSpecificationParameters);
+        }
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIsInitiator, mIsController, mSamplePeriod, mLocalDeviceAddress,
+                mRemoteDeviceAddresses, mChannelNumber, mTransmitPreambleCodeIndex,
+                mReceivePreambleCodeIndex, mStsPhyPacketType, mSpecificationParameters);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/uwb/RangingReport.java b/core/java/android/uwb/RangingReport.java
index 1d340b4..45180bf 100644
--- a/core/java/android/uwb/RangingReport.java
+++ b/core/java/android/uwb/RangingReport.java
@@ -17,11 +17,13 @@
 package android.uwb;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * This class contains the UWB ranging data
@@ -50,6 +52,31 @@
         return mRangingMeasurements;
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj instanceof RangingReport) {
+            RangingReport other = (RangingReport) obj;
+            return mRangingMeasurements.equals(other.getMeasurements());
+        }
+
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRangingMeasurements);
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/uwb/TEST_MAPPING b/core/java/android/uwb/TEST_MAPPING
new file mode 100644
index 0000000..9e50bd6
--- /dev/null
+++ b/core/java/android/uwb/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "UwbManagerTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 5780d4f..f4d5a7b 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
 import static android.view.InsetsController.AnimationType;
@@ -24,6 +25,7 @@
 import android.annotation.Nullable;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
+import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl.Transaction;
 import android.view.inputmethod.InputMethodManager;
@@ -105,6 +107,7 @@
     @Override
     void notifyHidden() {
         getImm().notifyImeHidden(mController.getHost().getWindowToken());
+        Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
     }
 
     @Override
diff --git a/core/java/android/view/InputApplicationHandle.java b/core/java/android/view/InputApplicationHandle.java
index 108345e..4abffde 100644
--- a/core/java/android/view/InputApplicationHandle.java
+++ b/core/java/android/view/InputApplicationHandle.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.os.IBinder;
 
 /**
@@ -31,17 +32,20 @@
     private long ptr;
 
     // Application name.
-    public String name;
+    public final @NonNull String name;
 
     // Dispatching timeout.
-    public long dispatchingTimeoutMillis;
+    public final long dispatchingTimeoutMillis;
 
-    public final IBinder token;
+    public final @NonNull IBinder token;
 
     private native void nativeDispose();
 
-    public InputApplicationHandle(IBinder token) {
+    public InputApplicationHandle(@NonNull IBinder token, @NonNull String name,
+            long dispatchingTimeoutMillis) {
         this.token = token;
+        this.name = name;
+        this.dispatchingTimeoutMillis = dispatchingTimeoutMillis;
     }
 
     public InputApplicationHandle(InputApplicationHandle handle) {
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index d1a9a05..5a34a92 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -37,7 +37,7 @@
     private long ptr;
 
     // The input application handle.
-    public final InputApplicationHandle inputApplicationHandle;
+    public InputApplicationHandle inputApplicationHandle;
 
     // The token associates input data with a window and its input channel. The client input
     // channel and the server input channel will both contain this token.
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index b5bf084..fbee833 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.view.InsetsControllerProto.CONTROL;
 import static android.view.InsetsControllerProto.STATE;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
@@ -829,6 +830,11 @@
     public void show(@InsetsType int types, boolean fromIme) {
         if (fromIme) {
             ImeTracing.getInstance().triggerDump();
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
+        } else {
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
         }
         // Handle pending request ready in case there was one set.
         if (fromIme && mPendingImeControlRequest != null) {
@@ -880,6 +886,9 @@
     void hide(@InsetsType int types, boolean fromIme) {
         if (fromIme) {
             ImeTracing.getInstance().triggerDump();
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
+        } else {
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
         }
         int typesReady = 0;
         final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
@@ -989,6 +998,7 @@
                 });
             }
             updateRequestedVisibility();
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
             return;
         }
 
@@ -1014,11 +1024,13 @@
             cancellationSignal.setOnCancelListener(() -> {
                 cancelAnimation(runner, true /* invokeCallback */);
             });
+        } else {
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
         }
         if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
-            showDirectly(types);
+            showDirectly(types, fromIme);
         } else {
-            hideDirectly(types, false /* animationFinished */, animationType);
+            hideDirectly(types, false /* animationFinished */, animationType, fromIme);
         }
         updateRequestedVisibility();
     }
@@ -1141,10 +1153,10 @@
         cancelAnimation(runner, false /* invokeCallback */);
         if (DEBUG) Log.d(TAG, "notifyFinished. shown: " + shown);
         if (shown) {
-            showDirectly(runner.getTypes());
+            showDirectly(runner.getTypes(), true /* fromIme */);
         } else {
             hideDirectly(runner.getTypes(), true /* animationFinished */,
-                    runner.getAnimationType());
+                    runner.getAnimationType(), true /* fromIme */);
         }
     }
 
@@ -1314,11 +1326,11 @@
                 show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
                 show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN,
                 !hasAnimationCallbacks /* useInsetsAnimationThread */);
-
     }
 
     private void hideDirectly(
-            @InsetsType int types, boolean animationFinished, @AnimationType int animationType) {
+            @InsetsType int types, boolean animationFinished, @AnimationType int animationType,
+            boolean fromIme) {
         if ((types & ime()) != 0) {
             ImeTracing.getInstance().triggerDump();
         }
@@ -1327,9 +1339,13 @@
             getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
         }
         updateRequestedVisibility();
+
+        if (fromIme) {
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
+        }
     }
 
-    private void showDirectly(@InsetsType int types) {
+    private void showDirectly(@InsetsType int types, boolean fromIme) {
         if ((types & ime()) != 0) {
             ImeTracing.getInstance().triggerDump();
         }
@@ -1338,6 +1354,10 @@
             getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
         }
         updateRequestedVisibility();
+
+        if (fromIme) {
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
+        }
     }
 
     /**
@@ -1374,7 +1394,7 @@
                 if (WARN) Log.w(TAG, "startAnimation canceled before preDraw");
                 return;
             }
-            Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW,
+            Trace.asyncTraceBegin(TRACE_TAG_VIEW,
                     "InsetsAnimation: " + WindowInsets.Type.toString(types), types);
             for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
                 RunningAnimation runningAnimation = mRunningAnimations.get(i);
@@ -1382,6 +1402,7 @@
                     runningAnimation.startDispatched = true;
                 }
             }
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
             mHost.dispatchWindowInsetsAnimationStart(animation, bounds);
             mStartingAnimation = true;
             controller.mReadyDispatched = true;
@@ -1392,7 +1413,7 @@
 
     @VisibleForTesting
     public void dispatchAnimationEnd(WindowInsetsAnimation animation) {
-        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW,
+        Trace.asyncTraceEnd(TRACE_TAG_VIEW,
                 "InsetsAnimation: " + WindowInsets.Type.toString(animation.getTypeMask()),
                 animation.getTypeMask());
         mHost.dispatchWindowInsetsAnimationEnd(animation);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 0e8cd54..9d24dff1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1890,8 +1890,10 @@
         mSurface.release();
         mSurfaceControl.release();
 
-        // We should probably add an explicit dispose.
-        mBlastBufferQueue = null;
+        if (mBlastBufferQueue != null) {
+            mBlastBufferQueue.destroy();
+            mBlastBufferQueue = null;
+        }
     }
 
     /**
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 6ec093e..bc3e35c 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -88,9 +88,11 @@
     public static final int FEATURE_VENDOR_FIRST = FEATURE_SYSTEM_LAST + 1;
 
     /**
-     * Registers a DisplayAreaOrganizer to manage display areas for a given feature.
+     * Registers a DisplayAreaOrganizer to manage display areas for a given feature. A feature can
+     * not be registered by multiple organizers at the same time.
      *
      * @return a list of display areas that should be managed by the organizer.
+     * @throws IllegalStateException if the feature has already been registered.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
     @CallSuper
diff --git a/core/java/android/window/IDisplayAreaOrganizerController.aidl b/core/java/android/window/IDisplayAreaOrganizerController.aidl
index 8943847..edabcf8 100644
--- a/core/java/android/window/IDisplayAreaOrganizerController.aidl
+++ b/core/java/android/window/IDisplayAreaOrganizerController.aidl
@@ -24,9 +24,11 @@
 interface IDisplayAreaOrganizerController {
 
     /**
-     * Registers a DisplayAreaOrganizer to manage display areas for a given feature.
+     * Registers a DisplayAreaOrganizer to manage display areas for a given feature. A feature can
+     * not be registered by multiple organizers at the same time.
      *
      * @return a list of display areas that should be managed by the organizer.
+     * @throws IllegalStateException if the feature has already been registered.
      */
     ParceledListSlice<DisplayAreaAppearedInfo> registerOrganizer(in IDisplayAreaOrganizer organizer,
         int displayAreaFeature);
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index f46626b..ffc7f05 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -144,7 +144,7 @@
     int HIDE_RECENTS_ANIMATION = 18;
 
     /**
-     * Hide soft input when {@link com.android.systemui.bubbles.BubbleController} is expanding,
+     * Hide soft input when {@link com.android.wm.shell.bubbles.BubbleController} is expanding,
      * switching, or collapsing Bubbles.
      */
     int HIDE_BUBBLES = 19;
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index ba07863..137430b 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -22,7 +22,12 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND;
@@ -67,6 +72,11 @@
     public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9;
     public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10;
     public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11;
+    public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12;
+    public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13;
+    public static final int CUJ_NOTIFICATION_ADD = 14;
+    public static final int CUJ_NOTIFICATION_REMOVE = 15;
+    public static final int CUJ_NOTIFICATION_APP_START = 16;
 
     private static final int NO_STATSD_LOGGING = -1;
 
@@ -87,6 +97,11 @@
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP,
             UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE,
+            UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH,
     };
 
     private static volatile InteractionJankMonitor sInstance;
@@ -112,6 +127,11 @@
             CUJ_LAUNCHER_APP_CLOSE_TO_HOME,
             CUJ_LAUNCHER_APP_CLOSE_TO_PIP,
             CUJ_LAUNCHER_QUICK_SWITCH,
+            CUJ_NOTIFICATION_HEADS_UP_APPEAR,
+            CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR,
+            CUJ_NOTIFICATION_ADD,
+            CUJ_NOTIFICATION_REMOVE,
+            CUJ_NOTIFICATION_APP_START,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 1123f20..dd1c87b 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -204,6 +204,9 @@
             ],
 
             shared_libs: [
+                "audioclient-types-aidl-unstable-cpp",
+                "audioflinger-aidl-unstable-cpp",
+                "av-types-aidl-unstable-cpp",
                 "libandroidicu",
                 "libbpf_android",
                 "libnetdbpf",
diff --git a/core/jni/android_hardware_input_InputApplicationHandle.cpp b/core/jni/android_hardware_input_InputApplicationHandle.cpp
index 7756a62..995bfa9 100644
--- a/core/jni/android_hardware_input_InputApplicationHandle.cpp
+++ b/core/jni/android_hardware_input_InputApplicationHandle.cpp
@@ -54,26 +54,28 @@
 
 bool NativeInputApplicationHandle::updateInfo() {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jobject obj = env->NewLocalRef(mObjWeak);
-    if (!obj) {
+    ScopedLocalRef<jobject> obj(env, env->NewLocalRef(mObjWeak));
+    if (!obj.get()) {
         return false;
     }
+    if (mInfo.token.get() != nullptr) {
+        // The java fields are immutable, so it doesn't need to update again.
+        return true;
+    }
 
-    mInfo.name = getStringField(env, obj, gInputApplicationHandleClassInfo.name, "<null>");
+    mInfo.name = getStringField(env, obj.get(), gInputApplicationHandleClassInfo.name, "<null>");
 
     mInfo.dispatchingTimeoutMillis =
-            env->GetLongField(obj, gInputApplicationHandleClassInfo.dispatchingTimeoutMillis);
+            env->GetLongField(obj.get(), gInputApplicationHandleClassInfo.dispatchingTimeoutMillis);
 
-    jobject tokenObj = env->GetObjectField(obj,
-            gInputApplicationHandleClassInfo.token);
-    if (tokenObj) {
-        mInfo.token = ibinderForJavaObject(env, tokenObj);
-        env->DeleteLocalRef(tokenObj);
+    ScopedLocalRef<jobject> tokenObj(env, env->GetObjectField(obj.get(),
+            gInputApplicationHandleClassInfo.token));
+    if (tokenObj.get()) {
+        mInfo.token = ibinderForJavaObject(env, tokenObj.get());
     } else {
         mInfo.token.clear();
     }
 
-    env->DeleteLocalRef(obj);
     return mInfo.token.get() != nullptr;
 }
 
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index cbce38e..12d8bc6 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -162,7 +162,7 @@
 
 static jint nativeGetKeyboardType(JNIEnv *env, jobject clazz, jlong ptr) {
     NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
-    return map->getMap()->getKeyboardType();
+    return static_cast<jint>(map->getMap()->getKeyboardType());
 }
 
 static jobjectArray nativeGetEvents(JNIEnv *env, jobject clazz, jlong ptr,
diff --git a/core/proto/android/server/alarm/alarmmanagerservice.proto b/core/proto/android/server/alarm/alarmmanagerservice.proto
index e1240245..8fe1bfc 100644
--- a/core/proto/android/server/alarm/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarm/alarmmanagerservice.proto
@@ -144,6 +144,8 @@
 
     repeated IdleDispatchEntryProto allow_while_idle_dispatches = 40;
     repeated WakeupEventProto recent_wakeup_history = 41;
+
+    repeated AlarmProto pending_alarms = 42;
 }
 
 // This is a soft wrapper for alarm clock information. It is not representative
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4df7d58..a778f14 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -349,7 +349,6 @@
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_PNO" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.DEVICE_IDLE" />
-    <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_DISPATCH" />
     <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_ACCEPTED" />
     <protected-broadcast android:name="com.android.server.action.REMOTE_BUGREPORT_SHARING_DECLINED" />
     <protected-broadcast android:name="com.android.internal.action.EUICC_FACTORY_RESET" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6e03398..082397e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -817,6 +817,30 @@
         <!-- B y-intercept --> <item>-0.198650895</item>
     </string-array>
 
+    <string-array name="config_reduceBrightColorsCoefficientsNative">
+        <!-- R a-coefficient --> <item>-0.691218457</item>
+        <!-- R b-coefficient --> <item>0.050135153</item>
+        <!-- R y-intercept --> <item>0.917684143</item>
+        <!-- G a-coefficient --> <item>-0.691218457</item>
+        <!-- G b-coefficient --> <item>0.050135153</item>
+        <!-- G y-intercept --> <item>0.917684143</item>
+        <!-- B a-coefficient --> <item>-0.691218457</item>
+        <!-- B b-coefficient --> <item>0.050135153</item>
+        <!-- B y-intercept --> <item>0.917684143</item>
+    </string-array>
+
+    <string-array name="config_reduceBrightColorsCoefficients">
+        <!-- R a-coefficient --> <item>0.00000000000000154</item>
+        <!-- R b-coefficient --> <item>-1.0</item>
+        <!-- R y-intercept --> <item>1.045977011</item>
+        <!-- G a-coefficient --> <item>0.00000000000000224</item>
+        <!-- G b-coefficient --> <item>-1.0</item>
+        <!-- G y-intercept --> <item>1.045977011</item>
+        <!-- B a-coefficient --> <item>0.0000000000000022</item>
+        <!-- B b-coefficient --> <item>-1.0</item>
+        <!-- B y-intercept --> <item>1.045977011</item>
+    </string-array>
+
     <!-- Boolean indicating whether display white balance is supported. -->
     <bool name="config_displayWhiteBalanceAvailable">false</bool>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a294c9d..4e4e3f8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3158,6 +3158,8 @@
   <java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
+  <java-symbol type="array" name="config_reduceBrightColorsCoefficients" />
+  <java-symbol type="array" name="config_reduceBrightColorsCoefficientsNative" />
   <java-symbol type="array" name="config_availableColorModes" />
   <java-symbol type="integer" name="config_accessibilityColorMode" />
   <java-symbol type="array" name="config_displayCompositionColorModes" />
diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
index faa67a8..1947c6c 100644
--- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java
@@ -26,22 +26,135 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.Arrays;
+
 @Presubmit
 @RunWith(JUnit4.class)
 public class CombinedVibrationEffectTest {
 
+    private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
+    private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
+
     @Test
     public void testValidateMono() {
-        CombinedVibrationEffect.createSynced(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        CombinedVibrationEffect.createSynced(VALID_EFFECT);
 
         assertThrows(IllegalArgumentException.class,
-                () -> CombinedVibrationEffect.createSynced(new VibrationEffect.OneShot(-1, -1)));
+                () -> CombinedVibrationEffect.createSynced(INVALID_EFFECT));
+    }
+
+    @Test
+    public void testValidateStereo() {
+        CombinedVibrationEffect.startSynced()
+                .addVibrator(0, VALID_EFFECT)
+                .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
+                .combine();
+        CombinedVibrationEffect.startSynced()
+                .addVibrator(0, INVALID_EFFECT)
+                .addVibrator(0, VALID_EFFECT)
+                .combine();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> CombinedVibrationEffect.startSynced()
+                        .addVibrator(0, INVALID_EFFECT)
+                        .combine());
+    }
+
+    @Test
+    public void testValidateSequential() {
+        CombinedVibrationEffect.startSequential()
+                .addNext(0, VALID_EFFECT)
+                .addNext(CombinedVibrationEffect.createSynced(VALID_EFFECT))
+                .combine();
+        CombinedVibrationEffect.startSequential()
+                .addNext(0, VALID_EFFECT)
+                .addNext(0, VALID_EFFECT, 100)
+                .combine();
+        CombinedVibrationEffect.startSequential()
+                .addNext(CombinedVibrationEffect.startSequential()
+                        .addNext(0, VALID_EFFECT)
+                        .combine())
+                .combine();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> CombinedVibrationEffect.startSequential()
+                        .addNext(0, VALID_EFFECT, -1)
+                        .combine());
+        assertThrows(IllegalArgumentException.class,
+                () -> CombinedVibrationEffect.startSequential()
+                        .addNext(0, INVALID_EFFECT)
+                        .combine());
+        assertThrows(IllegalArgumentException.class,
+                () -> new CombinedVibrationEffect.Sequential(
+                        Arrays.asList(CombinedVibrationEffect.startSequential()
+                                .addNext(CombinedVibrationEffect.createSynced(VALID_EFFECT))
+                                .combine()),
+                        Arrays.asList(0))
+                        .validate());
+    }
+
+    @Test
+    public void testNestedSequentialAccumulatesDelays() {
+        CombinedVibrationEffect.Sequential combined =
+                (CombinedVibrationEffect.Sequential) CombinedVibrationEffect.startSequential()
+                        .addNext(CombinedVibrationEffect.startSequential()
+                                        .addNext(0, VALID_EFFECT, /* delay= */ 100)
+                                        .addNext(1, VALID_EFFECT, /* delay= */ 100)
+                                        .combine(),
+                                /* delay= */ 10)
+                        .addNext(CombinedVibrationEffect.startSequential()
+                                .addNext(0, VALID_EFFECT, /* delay= */ 100)
+                                .combine())
+                        .addNext(CombinedVibrationEffect.startSequential()
+                                        .addNext(0, VALID_EFFECT)
+                                        .addNext(0, VALID_EFFECT, /* delay= */ 100)
+                                        .combine(),
+                                /* delay= */ 10)
+                        .combine();
+
+        assertEquals(Arrays.asList(110, 100, 100, 10, 100), combined.getDelays());
+    }
+
+    @Test
+    public void testCombineEmptyFails() {
+        assertThrows(IllegalStateException.class,
+                () -> CombinedVibrationEffect.startSynced().combine());
+        assertThrows(IllegalStateException.class,
+                () -> CombinedVibrationEffect.startSequential().combine());
     }
 
     @Test
     public void testSerializationMono() {
-        CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(
-                VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+        CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
+
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CombinedVibrationEffect restored = CombinedVibrationEffect.CREATOR.createFromParcel(parcel);
+        assertEquals(original, restored);
+    }
+
+    @Test
+    public void testSerializationStereo() {
+        CombinedVibrationEffect original = CombinedVibrationEffect.startSynced()
+                .addVibrator(0, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
+                .addVibrator(1, VibrationEffect.createOneShot(10, 255))
+                .combine();
+
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        CombinedVibrationEffect restored = CombinedVibrationEffect.CREATOR.createFromParcel(parcel);
+        assertEquals(original, restored);
+    }
+
+    @Test
+    public void testSerializationSequential() {
+        CombinedVibrationEffect original = CombinedVibrationEffect.startSequential()
+                .addNext(0, VALID_EFFECT)
+                .addNext(CombinedVibrationEffect.createSynced(VALID_EFFECT))
+                .addNext(0, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), 100)
+                .combine();
 
         Parcel parcel = Parcel.obtain();
         original.writeToParcel(parcel, 0);
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
index cc68bb6..4094f83 100644
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiAudioSystemClientTest.java
@@ -381,17 +381,31 @@
         }
 
         @Override
-        public List<String> getAllowedCecSettingValues(String name) {
+        public List<String> getAllowedCecSettingStringValues(String name) {
             return new ArrayList<>();
         }
 
         @Override
-        public String getCecSettingValue(String name) {
+        public int[] getAllowedCecSettingIntValues(String name) {
+            return new int[0];
+        }
+
+        @Override
+        public String getCecSettingStringValue(String name) {
             return "";
         }
 
         @Override
-        public void setCecSettingValue(String name, String value) {
+        public void setCecSettingStringValue(String name, String value) {
+        }
+
+        @Override
+        public int getCecSettingIntValue(String name) {
+            return 0;
+        }
+
+        @Override
+        public void setCecSettingIntValue(String name, int value) {
         }
     }
 
diff --git a/core/tests/uwbtests/Android.bp b/core/tests/uwbtests/Android.bp
new file mode 100644
index 0000000..c41c346
--- /dev/null
+++ b/core/tests/uwbtests/Android.bp
@@ -0,0 +1,28 @@
+// Copyright 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "UwbManagerTests",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/uwbtests/AndroidManifest.xml b/core/tests/uwbtests/AndroidManifest.xml
new file mode 100644
index 0000000..dc991ff
--- /dev/null
+++ b/core/tests/uwbtests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.uwb">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- This is a self-instrumenting test package. -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.uwb"
+                     android:label="UWB Manager Tests">
+    </instrumentation>
+
+</manifest>
+
diff --git a/core/tests/uwbtests/AndroidTest.xml b/core/tests/uwbtests/AndroidTest.xml
new file mode 100644
index 0000000..ff4b668
--- /dev/null
+++ b/core/tests/uwbtests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for UWB Manager test cases">
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-suite-tag" value="apct-instrumentation"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="UwbManagerTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct"/>
+    <option name="test-tag" value="UwbManagerTests"/>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.uwb" />
+        <option name="hidden-api-checks" value="false"/>
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+    </test>
+</configuration>
diff --git a/core/tests/uwbtests/src/android/uwb/AngleMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/AngleMeasurementTest.java
new file mode 100644
index 0000000..7769c28
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/AngleMeasurementTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link AngleMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AngleMeasurementTest {
+    private static final double EPSILON = 0.00000000001;
+
+    @Test
+    public void testBuilder() {
+        double radians = 0.1234;
+        double errorRadians = 0.5678;
+        double confidence = 0.5;
+
+        AngleMeasurement.Builder builder = new AngleMeasurement.Builder();
+        tryBuild(builder, false);
+
+        builder.setRadians(radians);
+        tryBuild(builder, false);
+
+        builder.setErrorRadians(errorRadians);
+        tryBuild(builder, false);
+
+        builder.setConfidenceLevel(confidence);
+        AngleMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(measurement.getRadians(), radians, 0);
+        assertEquals(measurement.getErrorRadians(), errorRadians, 0);
+        assertEquals(measurement.getConfidenceLevel(), confidence, 0);
+    }
+
+    private AngleMeasurement tryBuild(AngleMeasurement.Builder builder, boolean expectSuccess) {
+        AngleMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected AngleMeasurement.Builder.build() to fail, but it succeeded");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected AngleMeasurement.Builder.build() to succeed, but it failed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        AngleMeasurement measurement = UwbTestUtils.getAngleMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        AngleMeasurement fromParcel = AngleMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java
new file mode 100644
index 0000000..077b08f
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/AngleOfArrivalMeasurementTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link AngleOfArrivalMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AngleOfArrivalMeasurementTest {
+
+    @Test
+    public void testBuilder() {
+        AngleMeasurement azimuth = UwbTestUtils.getAngleMeasurement();
+        AngleMeasurement altitude = UwbTestUtils.getAngleMeasurement();
+
+        AngleOfArrivalMeasurement.Builder builder = new AngleOfArrivalMeasurement.Builder();
+        tryBuild(builder, false);
+
+        builder.setAltitudeAngleMeasurement(altitude);
+        tryBuild(builder, false);
+
+        builder.setAzimuthAngleMeasurement(azimuth);
+        AngleOfArrivalMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(azimuth, measurement.getAzimuth());
+        assertEquals(altitude, measurement.getAltitude());
+    }
+
+    private AngleMeasurement getAngleMeasurement(double radian, double error, double confidence) {
+        return new AngleMeasurement.Builder()
+                .setRadians(radian)
+                .setErrorRadians(error)
+                .setConfidenceLevel(confidence)
+                .build();
+    }
+
+    private AngleOfArrivalMeasurement tryBuild(AngleOfArrivalMeasurement.Builder builder,
+            boolean expectSuccess) {
+        AngleOfArrivalMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected AngleOfArrivalMeasurement.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected AngleOfArrivalMeasurement.Builder.build() to succeed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        AngleOfArrivalMeasurement measurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        AngleOfArrivalMeasurement fromParcel =
+                AngleOfArrivalMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/DistanceMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/DistanceMeasurementTest.java
new file mode 100644
index 0000000..439c884
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/DistanceMeasurementTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link DistanceMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DistanceMeasurementTest {
+    private static final double EPSILON = 0.00000000001;
+
+    @Test
+    public void testBuilder() {
+        double meters = 0.12;
+        double error = 0.54;
+        double confidence = 0.99;
+
+        DistanceMeasurement.Builder builder = new DistanceMeasurement.Builder();
+        tryBuild(builder, false);
+
+        builder.setMeters(meters);
+        tryBuild(builder, false);
+
+        builder.setErrorMeters(error);
+        tryBuild(builder, false);
+
+        builder.setConfidenceLevel(confidence);
+        DistanceMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(meters, measurement.getMeters(), 0);
+        assertEquals(error, measurement.getErrorMeters(), 0);
+        assertEquals(confidence, measurement.getConfidenceLevel(), 0);
+    }
+
+    private DistanceMeasurement tryBuild(DistanceMeasurement.Builder builder,
+            boolean expectSuccess) {
+        DistanceMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected DistanceMeasurement.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected DistanceMeasurement.Builder.build() to succeed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        DistanceMeasurement measurement = UwbTestUtils.getDistanceMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        DistanceMeasurement fromParcel =
+                DistanceMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingMeasurementTest.java b/core/tests/uwbtests/src/android/uwb/RangingMeasurementTest.java
new file mode 100644
index 0000000..a7559d8
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/RangingMeasurementTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link RangingMeasurement}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingMeasurementTest {
+
+    @Test
+    public void testBuilder() {
+        int status = RangingMeasurement.RANGING_STATUS_SUCCESS;
+        UwbAddress address = UwbTestUtils.getUwbAddress(false);
+        long time = SystemClock.elapsedRealtimeNanos();
+        AngleOfArrivalMeasurement angleMeasurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+        DistanceMeasurement distanceMeasurement = UwbTestUtils.getDistanceMeasurement();
+
+        RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
+
+        builder.setStatus(status);
+        tryBuild(builder, false);
+
+        builder.setElapsedRealtimeNanos(time);
+        tryBuild(builder, false);
+
+        builder.setAngleOfArrivalMeasurement(angleMeasurement);
+        tryBuild(builder, false);
+
+        builder.setDistanceMeasurement(distanceMeasurement);
+        tryBuild(builder, false);
+
+        builder.setRemoteDeviceAddress(address);
+        RangingMeasurement measurement = tryBuild(builder, true);
+
+        assertEquals(status, measurement.getStatus());
+        assertEquals(address, measurement.getRemoteDeviceAddress());
+        assertEquals(time, measurement.getElapsedRealtimeNanos());
+        assertEquals(angleMeasurement, measurement.getAngleOfArrival());
+        assertEquals(distanceMeasurement, measurement.getDistance());
+    }
+
+    private RangingMeasurement tryBuild(RangingMeasurement.Builder builder,
+            boolean expectSuccess) {
+        RangingMeasurement measurement = null;
+        try {
+            measurement = builder.build();
+            if (!expectSuccess) {
+                fail("Expected RangingMeasurement.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected DistanceMeasurement.Builder.build() to succeed");
+            }
+        }
+        return measurement;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        RangingMeasurement measurement = UwbTestUtils.getRangingMeasurement();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        RangingMeasurement fromParcel = RangingMeasurement.CREATOR.createFromParcel(parcel);
+        assertEquals(measurement, fromParcel);
+    }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java b/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java
new file mode 100644
index 0000000..8095c99
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/RangingParamsTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.PersistableBundle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+
+/**
+ * Test of {@link RangingParams}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingParamsTest {
+
+    @Test
+    public void testParams_Build() {
+        UwbAddress local = UwbAddress.fromBytes(new byte[] {(byte) 0xA0, (byte) 0x57});
+        UwbAddress remote = UwbAddress.fromBytes(new byte[] {(byte) 0x4D, (byte) 0x8C});
+        int channel = 9;
+        int rxPreamble = 16;
+        int txPreamble = 21;
+        boolean isController = true;
+        boolean isInitiator = false;
+        @RangingParams.StsPhyPacketType int stsPhyType = RangingParams.STS_PHY_PACKET_TYPE_SP2;
+        Duration samplePeriod = Duration.ofSeconds(1, 234);
+        PersistableBundle specParams = new PersistableBundle();
+        specParams.putString("protocol", "some_protocol");
+
+        RangingParams params = new RangingParams.Builder()
+                .setChannelNumber(channel)
+                .setReceivePreambleCodeIndex(rxPreamble)
+                .setTransmitPreambleCodeIndex(txPreamble)
+                .setLocalDeviceAddress(local)
+                .addRemoteDeviceAddress(remote)
+                .setIsController(isController)
+                .setIsInitiator(isInitiator)
+                .setSamplePeriod(samplePeriod)
+                .setStsPhPacketType(stsPhyType)
+                .setSpecificationParameters(specParams)
+                .build();
+
+        assertEquals(params.getLocalDeviceAddress(), local);
+        assertEquals(params.getRemoteDeviceAddresses().size(), 1);
+        assertEquals(params.getRemoteDeviceAddresses().get(0), remote);
+        assertEquals(params.getChannelNumber(), channel);
+        assertEquals(params.isController(), isController);
+        assertEquals(params.isInitiator(), isInitiator);
+        assertEquals(params.getRxPreambleIndex(), rxPreamble);
+        assertEquals(params.getTxPreambleIndex(), txPreamble);
+        assertEquals(params.getStsPhyPacketType(), stsPhyType);
+        assertEquals(params.getSamplingPeriod(), samplePeriod);
+        assertTrue(params.getSpecificationParameters().kindofEquals(specParams));
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        RangingParams params = new RangingParams.Builder()
+                .setChannelNumber(9)
+                .setReceivePreambleCodeIndex(16)
+                .setTransmitPreambleCodeIndex(21)
+                .setLocalDeviceAddress(UwbTestUtils.getUwbAddress(false))
+                .addRemoteDeviceAddress(UwbTestUtils.getUwbAddress(true))
+                .setIsController(false)
+                .setIsInitiator(true)
+                .setSamplePeriod(Duration.ofSeconds(2))
+                .setStsPhPacketType(RangingParams.STS_PHY_PACKET_TYPE_SP1)
+                .build();
+        params.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        RangingParams fromParcel = RangingParams.CREATOR.createFromParcel(parcel);
+        assertEquals(params, fromParcel);
+    }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/RangingReportTest.java b/core/tests/uwbtests/src/android/uwb/RangingReportTest.java
new file mode 100644
index 0000000..64c48ba
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/RangingReportTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * Test of {@link RangingReport}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RangingReportTest {
+
+    @Test
+    public void testBuilder() {
+        List<RangingMeasurement> measurements = UwbTestUtils.getRangingMeasurements(5);
+
+        RangingReport.Builder builder = new RangingReport.Builder();
+        builder.addMeasurements(measurements);
+        RangingReport report = tryBuild(builder, true);
+        verifyMeasurementsEqual(measurements, report.getMeasurements());
+
+
+        builder = new RangingReport.Builder();
+        for (RangingMeasurement measurement : measurements) {
+            builder.addMeasurement(measurement);
+        }
+        report = tryBuild(builder, true);
+        verifyMeasurementsEqual(measurements, report.getMeasurements());
+    }
+
+    private void verifyMeasurementsEqual(List<RangingMeasurement> expected,
+            List<RangingMeasurement> actual) {
+        assertEquals(expected.size(), actual.size());
+        for (int i = 0; i < expected.size(); i++) {
+            assertEquals(expected.get(i), actual.get(i));
+        }
+    }
+
+    private RangingReport tryBuild(RangingReport.Builder builder,
+            boolean expectSuccess) {
+        RangingReport report = null;
+        try {
+            report = builder.build();
+            if (!expectSuccess) {
+                fail("Expected RangingReport.Builder.build() to fail");
+            }
+        } catch (IllegalStateException e) {
+            if (expectSuccess) {
+                fail("Expected RangingReport.Builder.build() to succeed");
+            }
+        }
+        return report;
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        RangingReport report = UwbTestUtils.getRangingReports(5);
+        report.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        RangingReport fromParcel = RangingReport.CREATOR.createFromParcel(parcel);
+        assertEquals(report, fromParcel);
+    }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/UwbAddressTest.java b/core/tests/uwbtests/src/android/uwb/UwbAddressTest.java
new file mode 100644
index 0000000..ccc88a9
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/UwbAddressTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test of {@link UwbAddress}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UwbAddressTest {
+
+    @Test
+    public void testFromBytes_Short() {
+        runFromBytes(UwbAddress.SHORT_ADDRESS_BYTE_LENGTH);
+    }
+
+    @Test
+    public void testFromBytes_Extended() {
+        runFromBytes(UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH);
+    }
+
+    private void runFromBytes(int len) {
+        byte[] addressBytes = getByteArray(len);
+        UwbAddress address = UwbAddress.fromBytes(addressBytes);
+        assertEquals(address.size(), len);
+        assertEquals(addressBytes, address.toBytes());
+    }
+
+    private byte[] getByteArray(int len) {
+        byte[] res = new byte[len];
+        for (int i = 0; i < len; i++) {
+            res[i] = (byte) i;
+        }
+        return res;
+    }
+
+    @Test
+    public void testParcel_Short() {
+        runParcel(true);
+    }
+
+    @Test
+    public void testParcel_Extended() {
+        runParcel(false);
+    }
+
+    private void runParcel(boolean useShortAddress) {
+        Parcel parcel = Parcel.obtain();
+        UwbAddress address = UwbTestUtils.getUwbAddress(useShortAddress);
+        address.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        UwbAddress fromParcel = UwbAddress.CREATOR.createFromParcel(parcel);
+        assertEquals(address, fromParcel);
+    }
+}
diff --git a/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java
new file mode 100644
index 0000000..62e0b62
--- /dev/null
+++ b/core/tests/uwbtests/src/android/uwb/UwbTestUtils.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.uwb;
+
+import android.os.SystemClock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UwbTestUtils {
+    private UwbTestUtils() {}
+
+    public static AngleMeasurement getAngleMeasurement() {
+        return new AngleMeasurement.Builder()
+                .setRadians(getDoubleInRange(-Math.PI, Math.PI))
+                .setErrorRadians(getDoubleInRange(0, Math.PI))
+                .setConfidenceLevel(getDoubleInRange(0, 1))
+                .build();
+    }
+
+    public static AngleOfArrivalMeasurement getAngleOfArrivalMeasurement() {
+        return new AngleOfArrivalMeasurement.Builder()
+                .setAltitudeAngleMeasurement(getAngleMeasurement())
+                .setAzimuthAngleMeasurement(getAngleMeasurement())
+                .build();
+    }
+
+    public static DistanceMeasurement getDistanceMeasurement() {
+        return new DistanceMeasurement.Builder()
+                .setMeters(getDoubleInRange(0, 100))
+                .setErrorMeters(getDoubleInRange(0, 10))
+                .setConfidenceLevel(getDoubleInRange(0, 1))
+                .build();
+    }
+
+    public static RangingMeasurement getRangingMeasurement() {
+        return getRangingMeasurement(getUwbAddress(false));
+    }
+
+    public static RangingMeasurement getRangingMeasurement(UwbAddress address) {
+        return new RangingMeasurement.Builder()
+                .setDistanceMeasurement(getDistanceMeasurement())
+                .setAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement())
+                .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
+                .setRemoteDeviceAddress(address != null ? address : getUwbAddress(false))
+                .setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS)
+                .build();
+    }
+
+    public static List<RangingMeasurement> getRangingMeasurements(int num) {
+        List<RangingMeasurement> result = new ArrayList<>();
+        for (int i = 0; i < num; i++) {
+            result.add(getRangingMeasurement());
+        }
+        return result;
+    }
+
+    public static RangingReport getRangingReports(int numMeasurements) {
+        RangingReport.Builder builder = new RangingReport.Builder();
+        for (int i = 0; i < numMeasurements; i++) {
+            builder.addMeasurement(getRangingMeasurement());
+        }
+        return builder.build();
+    }
+
+    private static double getDoubleInRange(double min, double max) {
+        return min + (max - min) * Math.random();
+    }
+
+    public static UwbAddress getUwbAddress(boolean isShortAddress) {
+        byte[] addressBytes = new byte[isShortAddress ? UwbAddress.SHORT_ADDRESS_BYTE_LENGTH :
+                UwbAddress.EXTENDED_ADDRESS_BYTE_LENGTH];
+        for (int i = 0; i < addressBytes.length; i++) {
+            addressBytes[i] = (byte) getDoubleInRange(1, 255);
+        }
+        return UwbAddress.fromBytes(addressBytes);
+    }
+}
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index b143be7..441c163 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -327,7 +327,7 @@
      * 1) Create Typeface from ttf file.
      * <pre>
      * <code>
-     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
+     * Typeface.Builder builder = new Typeface.Builder("your_font_file.ttf");
      * Typeface typeface = builder.build();
      * </code>
      * </pre>
@@ -335,7 +335,7 @@
      * 2) Create Typeface from ttc file in assets directory.
      * <pre>
      * <code>
-     * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
+     * Typeface.Builder builder = new Typeface.Builder(getAssets(), "your_font_file.ttc");
      * builder.setTtcIndex(2);  // Set index of font collection.
      * Typeface typeface = builder.build();
      * </code>
@@ -344,7 +344,7 @@
      * 3) Create Typeface with variation settings.
      * <pre>
      * <code>
-     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
+     * Typeface.Builder builder = new Typeface.Builder("your_font_file.ttf");
      * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
      * builder.setWeight(700);  // Tell the system that this is a bold font.
      * builder.setItalic(true);  // Tell the system that this is an italic style font.
diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java
index 586c512..c07b4bc 100644
--- a/graphics/java/android/graphics/fonts/Font.java
+++ b/graphics/java/android/graphics/fonts/Font.java
@@ -626,6 +626,51 @@
         return mNativePtr;
     }
 
+    /**
+     * Returns true if the given font is created from the same source data from this font.
+     *
+     * This method essentially compares {@link ByteBuffer} inside Font, but has some optimization
+     * for faster comparing. This method compares the internal object before going to one-by-one
+     * byte compare with {@link ByteBuffer}. This typically works efficiently if you compares the
+     * font that is created from {@link Builder#Builder(Font)}.
+     *
+     * This API is typically useful for checking if two fonts can be interpolated by font variation
+     * axes. For example, when you call {@link android.text.TextShaper} for the same
+     * string but different style, you may get two font objects which is created from the same
+     * source but have different parameters. You may want to animate between them by interpolating
+     * font variation settings if these fonts are created from the same source.
+     *
+     * @param other a font object to be compared.
+     * @return true if given font is created from the same source from this font. Otherwise false.
+     */
+    public boolean isSameSource(@NonNull Font other) {
+        Objects.requireNonNull(other);
+
+        // Shortcut for the same instance.
+        if (mBuffer == other.mBuffer) {
+            return true;
+        }
+
+        // Shortcut for different font buffer check by comparing size.
+        if (mBuffer.capacity() != other.mBuffer.capacity()) {
+            return false;
+        }
+
+        // ByteBuffer#equals compares all bytes which is not performant for e.g HashMap. Since
+        // underlying native font object holds buffer address, check if this buffer points exactly
+        // the same address as a shortcut of equality. For being compatible with of API30 or before,
+        // check buffer position even if the buffer points the same address.
+        if (nIsSameBufferAddress(mNativePtr, other.mNativePtr)
+                && mBuffer.position() == other.mBuffer.position()) {
+            return true;
+        }
+
+        // Unfortunately, need to compare bytes one-by-one since the buffer may be different font
+        // file but has the same file size, or two font has same content but they are allocated
+        // differently. For being compatible with API30 ore before, compare with ByteBuffer#equals.
+        return mBuffer.equals(other.mBuffer);
+    }
+
     @Override
     public boolean equals(@Nullable Object o) {
         if (o == this) {
@@ -643,24 +688,7 @@
             return false;
         }
 
-        // Shortcut for different font buffer check by comparing size.
-        if (mBuffer.capacity() != f.mBuffer.capacity()) {
-            return false;
-        }
-
-        // ByteBuffer#equals compares all bytes which is not performant for e.g HashMap. Since
-        // underlying native font object holds buffer address, check if this buffer points exactly
-        // the same address as a shortcut of equality. For being compatible with of API30 or before,
-        // check buffer position even if the buffer points the same address.
-        if (nIsSameBufferAddress(mNativePtr, f.mNativePtr)
-                && mBuffer.position() == f.mBuffer.position()) {
-            return true;
-        }
-
-        // Unfortunately, need to compare bytes one-by-one since the buffer may be different font
-        // file but has the same file size, or two font has same content but they are allocated
-        // differently. For being compatible with API30 ore before, compare with ByteBuffer#equals.
-        return mBuffer.equals(f.mBuffer);
+        return isSameSource(f);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 0defbd6..39e32c6 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -116,12 +116,15 @@
         "res",
     ],
     static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx.arch.core_core-runtime",
         "androidx.dynamicanimation_dynamicanimation",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
+        "iconloader_base",
         "protolog-lib",
+        "SettingsLib",
         "WindowManager-Shell-proto",
-        "androidx.appcompat_appcompat",
     ],
     kotlincflags: ["-Xjvm-default=enable"],
     manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/libs/WindowManager/Shell/res/drawable/bubble_dismiss_circle.xml
similarity index 62%
copy from packages/SystemUI/res/layout/bubble_view.xml
copy to libs/WindowManager/Shell/res/drawable/bubble_dismiss_circle.xml
index 78f7cff..2104be4 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/libs/WindowManager/Shell/res/drawable/bubble_dismiss_circle.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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.
@@ -12,10 +11,18 @@
   ~ 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
+  ~ limitations under the License.
   -->
-<com.android.systemui.bubbles.BadgedImageView
+<!--
+    The transparent circle outline that encircles the bubbles when they're in the dismiss target.
+-->
+<shape
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/bubble_view"
-    android:layout_width="@dimen/individual_bubble_size"
-    android:layout_height="@dimen/individual_bubble_size"/>
+    android:shape="oval">
+
+    <stroke
+        android:width="1dp"
+        android:color="#66FFFFFF" />
+
+    <solid android:color="#B3000000" />
+</shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_dismiss_icon.xml b/libs/WindowManager/Shell/res/drawable/bubble_dismiss_icon.xml
new file mode 100644
index 0000000..ff8fede
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_dismiss_icon.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+<!-- The 'X' bubble dismiss icon. This is just ic_close with a stroke. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+        android:fillColor="#FFFFFFFF"
+        android:strokeColor="#FF000000"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_ic_create_bubble.xml b/libs/WindowManager/Shell/res/drawable/bubble_ic_create_bubble.xml
new file mode 100644
index 0000000..920671a2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_ic_create_bubble.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M23,5v8h-2V5H3v14h10v2v0H3c-1.1,0 -2,-0.9 -2,-2V5c0,-1.1 0.9,-2 2,-2h18C22.1,3 23,3.9 23,5zM10,8v2.59L5.71,6.29L4.29,7.71L8.59,12H6v2h6V8H10zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml b/libs/WindowManager/Shell/res/drawable/bubble_ic_empty_overflow_dark.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_empty_bubble_overflow_dark.xml
rename to libs/WindowManager/Shell/res/drawable/bubble_ic_empty_overflow_dark.xml
diff --git a/packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml b/libs/WindowManager/Shell/res/drawable/bubble_ic_empty_overflow_light.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_empty_bubble_overflow_light.xml
rename to libs/WindowManager/Shell/res/drawable/bubble_ic_empty_overflow_light.xml
diff --git a/packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml b/libs/WindowManager/Shell/res/drawable/bubble_ic_overflow_button.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml
rename to libs/WindowManager/Shell/res/drawable/bubble_ic_overflow_button.xml
diff --git a/libs/WindowManager/Shell/res/drawable/bubble_ic_stop_bubble.xml b/libs/WindowManager/Shell/res/drawable/bubble_ic_stop_bubble.xml
new file mode 100644
index 0000000..8609576
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/bubble_ic_stop_bubble.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M11.29,14.71L7,10.41V13H5V7h6v2H8.41l4.29,4.29L11.29,14.71zM21,3H3C1.9,3 1,3.9 1,5v14c0,1.1 0.9,2 2,2h10v0v-2H3V5h18v8h2V5C23,3.9 22.1,3 21,3zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_row.xml
similarity index 95%
rename from packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
rename to libs/WindowManager/Shell/res/drawable/bubble_manage_menu_row.xml
index a793680..c61ac1c 100644
--- a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
+++ b/libs/WindowManager/Shell/res/drawable/bubble_manage_menu_row.xml
@@ -12,7 +12,7 @@
   ~ 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
+  ~ limitations under the License.
   -->
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:state_pressed="true">
diff --git a/packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml b/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/bubble_stack_user_education_bg.xml
rename to libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg.xml
diff --git a/packages/SystemUI/res/drawable/bubble_stack_user_education_bg_rtl.xml b/libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg_rtl.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/bubble_stack_user_education_bg_rtl.xml
rename to libs/WindowManager/Shell/res/drawable/bubble_stack_user_education_bg_rtl.xml
diff --git a/libs/WindowManager/Shell/res/drawable/ic_remove_no_shadow.xml b/libs/WindowManager/Shell/res/drawable/ic_remove_no_shadow.xml
new file mode 100644
index 0000000..265c501
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_remove_no_shadow.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/textColorPrimary" >
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M13.41,12l5.29-5.29c0.39-0.39,0.39-1.02,0-1.41c-0.39-0.39-1.02-0.39-1.41,0L12,10.59L6.71,
+        5.29c-0.39-0.39-1.02-0.39-1.41,0c-0.39,0.39-0.39,1.02,0,1.41L10.59,12l-5.29,5.29c-0.39,0.39-0.39,1.02,
+        0,1.41c0.39,0.39,1.02,0.39,1.41,0L12,13.41l5.29,5.29c0.39,0.39,1.02,0.39,1.41,0c0.39-0.39,0.39-1.02,0-1.41L13.41,12z"/>
+</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/rounded_bg_full.xml b/libs/WindowManager/Shell/res/drawable/rounded_bg_full.xml
new file mode 100644
index 0000000..e957445
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/rounded_bg_full.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="?android:attr/colorBackgroundFloating" />
+    <corners
+        android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+        android:topLeftRadius="?android:attr/dialogCornerRadius"
+        android:bottomRightRadius="?android:attr/dialogCornerRadius"
+        android:topRightRadius="?android:attr/dialogCornerRadius"
+        />
+</shape>
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/libs/WindowManager/Shell/res/layout/bubble_dismiss_target.xml
similarity index 100%
rename from packages/SystemUI/res/layout/bubble_dismiss_target.xml
rename to libs/WindowManager/Shell/res/layout/bubble_dismiss_target.xml
diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_expanded_view.xml
similarity index 80%
rename from packages/SystemUI/res/layout/bubble_expanded_view.xml
rename to libs/WindowManager/Shell/res/layout/bubble_expanded_view.xml
index db40c4f..54b08c6 100644
--- a/packages/SystemUI/res/layout/bubble_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_expanded_view.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2018 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.
@@ -12,9 +12,9 @@
   ~ 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
+  ~ limitations under the License.
   -->
-<com.android.systemui.bubbles.BubbleExpandedView
+<com.android.wm.shell.bubbles.BubbleExpandedView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
     android:layout_width="match_parent"
@@ -27,7 +27,7 @@
         android:layout_height="@dimen/bubble_pointer_height"
     />
 
-    <com.android.systemui.statusbar.AlphaOptimizedButton
+    <com.android.wm.shell.common.AlphaOptimizedButton
         style="@android:style/Widget.Material.Button.Borderless"
         android:id="@+id/settings_button"
         android:layout_gravity="start"
@@ -35,7 +35,7 @@
         android:layout_height="wrap_content"
         android:focusable="true"
         android:text="@string/manage_bubbles_text"
-        android:textColor="?attr/wallpaperTextColor"
+        android:textColor="?android:attr/textColorPrimaryInverse"
     />
 
-</com.android.systemui.bubbles.BubbleExpandedView>
+</com.android.wm.shell.bubbles.BubbleExpandedView>
diff --git a/packages/SystemUI/res/layout/bubble_flyout.xml b/libs/WindowManager/Shell/res/layout/bubble_flyout.xml
similarity index 97%
rename from packages/SystemUI/res/layout/bubble_flyout.xml
rename to libs/WindowManager/Shell/res/layout/bubble_flyout.xml
index 22a8135..7fdf290 100644
--- a/packages/SystemUI/res/layout/bubble_flyout.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_flyout.xml
@@ -34,7 +34,7 @@
             android:layout_height="30dp"
             android:layout_marginEnd="@dimen/bubble_flyout_avatar_message_space"
             android:scaleType="centerInside"
-            android:src="@drawable/ic_create_bubble"/>
+            android:src="@drawable/bubble_ic_create_bubble"/>
 
         <LinearLayout
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
similarity index 95%
rename from packages/SystemUI/res/layout/bubble_manage_menu.xml
rename to libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
index 8494882..3a6aa80 100644
--- a/packages/SystemUI/res/layout/bubble_manage_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml
@@ -35,7 +35,7 @@
             android:layout_width="24dp"
             android:layout_height="24dp"
             android:src="@drawable/ic_remove_no_shadow"
-            android:tint="@color/global_actions_text"/>
+            android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
             android:layout_width="wrap_content"
@@ -59,8 +59,8 @@
         <ImageView
             android:layout_width="24dp"
             android:layout_height="24dp"
-            android:src="@drawable/ic_stop_bubble"
-            android:tint="@color/global_actions_text"/>
+            android:src="@drawable/bubble_ic_stop_bubble"
+            android:tint="@color/bubbles_icon_tint"/>
 
         <TextView
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/bubble_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
similarity index 65%
rename from packages/SystemUI/res/layout/bubble_menu_view.xml
rename to libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
index 24608d3..0c1d1a5 100644
--- a/packages/SystemUI/res/layout/bubble_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_menu_view.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.bubbles.BubbleMenuView
+<com.android.wm.shell.bubbles.BubbleMenuView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
@@ -30,14 +30,14 @@
 
         <ImageView
             android:id="@*android:id/icon"
-            android:layout_width="@dimen/global_actions_grid_item_icon_width"
-            android:layout_height="@dimen/global_actions_grid_item_icon_height"
-            android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
-            android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
-            android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
-            android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+            android:layout_width="@dimen/bubble_grid_item_icon_width"
+            android:layout_height="@dimen/bubble_grid_item_icon_height"
+            android:layout_marginTop="@dimen/bubble_grid_item_icon_top_margin"
+            android:layout_marginBottom="@dimen/bubble_grid_item_icon_bottom_margin"
+            android:layout_marginLeft="@dimen/bubble_grid_item_icon_side_margin"
+            android:layout_marginRight="@dimen/bubble_grid_item_icon_side_margin"
             android:scaleType="centerInside"
-            android:tint="@color/global_actions_text"
+            android:tint="@color/bubbles_icon_tint"
         />
     </FrameLayout>
-</com.android.systemui.bubbles.BubbleMenuView>
+</com.android.wm.shell.bubbles.BubbleMenuView>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_activity.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_activity.xml
similarity index 100%
rename from packages/SystemUI/res/layout/bubble_overflow_activity.xml
rename to libs/WindowManager/Shell/res/layout/bubble_overflow_activity.xml
diff --git a/packages/SystemUI/res/layout/bubble_overflow_button.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
similarity index 89%
rename from packages/SystemUI/res/layout/bubble_overflow_button.xml
rename to libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
index 8f0fd4f..61000fe 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_button.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_button.xml
@@ -14,9 +14,9 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.systemui.bubbles.BadgedImageView
+<com.android.wm.shell.bubbles.BadgedImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/bubble_overflow_button"
     android:layout_width="@dimen/individual_bubble_size"
     android:layout_height="@dimen/individual_bubble_size"
-    android:src="@drawable/ic_bubble_overflow_button"/>
+    android:src="@drawable/bubble_ic_overflow_button"/>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
similarity index 96%
rename from packages/SystemUI/res/layout/bubble_overflow_view.xml
rename to libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
index 1218fba..c1f67bd 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_view.xml
@@ -21,7 +21,7 @@
     android:layout_height="wrap_content"
     android:orientation="vertical">
 
-    <com.android.systemui.bubbles.BadgedImageView
+    <com.android.wm.shell.bubbles.BadgedImageView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/bubble_view"
         android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/bubble_stack_user_education.xml b/libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
similarity index 100%
rename from packages/SystemUI/res/layout/bubble_stack_user_education.xml
rename to libs/WindowManager/Shell/res/layout/bubble_stack_user_education.xml
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/libs/WindowManager/Shell/res/layout/bubble_view.xml
similarity index 94%
rename from packages/SystemUI/res/layout/bubble_view.xml
rename to libs/WindowManager/Shell/res/layout/bubble_view.xml
index 78f7cff..a28bd678 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_view.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.systemui.bubbles.BadgedImageView
+<com.android.wm.shell.bubbles.BadgedImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/bubble_view"
     android:layout_width="@dimen/individual_bubble_size"
diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
similarity index 92%
rename from packages/SystemUI/res/layout/bubbles_manage_button_education.xml
rename to libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
index b51dc93..8de06c7 100644
--- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubbles_manage_button_education.xml
@@ -64,7 +64,7 @@
         android:id="@+id/button_layout"
         android:orientation="horizontal" >
 
-        <com.android.systemui.statusbar.AlphaOptimizedButton
+        <com.android.wm.shell.common.AlphaOptimizedButton
             style="@android:style/Widget.Material.Button.Borderless"
             android:id="@+id/manage"
             android:layout_gravity="start"
@@ -73,10 +73,10 @@
             android:focusable="true"
             android:clickable="false"
             android:text="@string/manage_bubbles_text"
-            android:textColor="?attr/wallpaperTextColor"
+            android:textColor="?android:attr/textColorPrimaryInverse"
             />
 
-        <com.android.systemui.statusbar.AlphaOptimizedButton
+        <com.android.wm.shell.common.AlphaOptimizedButton
             style="@android:style/Widget.Material.Button.Borderless"
             android:id="@+id/got_it"
             android:layout_gravity="start"
@@ -84,7 +84,7 @@
             android:layout_height="wrap_content"
             android:focusable="true"
             android:text="@string/bubbles_user_education_got_it"
-            android:textColor="?attr/wallpaperTextColor"
+            android:textColor="?android:attr/textColorPrimaryInverse"
             />
     </LinearLayout>
 </LinearLayout>
diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
index bee9f41..6342c00 100644
--- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
+++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json
@@ -79,12 +79,6 @@
       "group": "WM_SHELL_TASK_ORG",
       "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java"
     },
-    "-712674749": {
-      "message": "Clip description: %s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
     "-710770147": {
       "message": "Add target: %s",
       "level": "VERBOSE",
@@ -121,6 +115,12 @@
       "group": "WM_SHELL_DRAG_AND_DROP",
       "at": "com\/android\/wm\/shell\/draganddrop\/DropOutlineDrawable.java"
     },
+    "375908576": {
+      "message": "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
+      "level": "VERBOSE",
+      "group": "WM_SHELL_DRAG_AND_DROP",
+      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
+    },
     "481673835": {
       "message": "addListenerForTaskId taskId=%s",
       "level": "VERBOSE",
@@ -169,12 +169,6 @@
       "group": "WM_SHELL_DRAG_AND_DROP",
       "at": "com\/android\/wm\/shell\/draganddrop\/DragLayout.java"
     },
-    "1842752748": {
-      "message": "Clip description: handlingDrag=%b mimeTypes=%s",
-      "level": "VERBOSE",
-      "group": "WM_SHELL_DRAG_AND_DROP",
-      "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java"
-    },
     "1862198614": {
       "message": "Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/res/values-land/dimens.xml b/libs/WindowManager/Shell/res/values-land/dimens.xml
index 77a601d..aafba58 100644
--- a/libs/WindowManager/Shell/res/values-land/dimens.xml
+++ b/libs/WindowManager/Shell/res/values-land/dimens.xml
@@ -18,4 +18,8 @@
 <resources>
     <dimen name="docked_divider_handle_width">2dp</dimen>
     <dimen name="docked_divider_handle_height">16dp</dimen>
+
+    <!-- Padding between status bar and bubbles when displayed in expanded state, smaller
+     value in landscape since we have limited vertical space-->
+    <dimen name="bubble_padding_top">4dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/libs/WindowManager/Shell/res/values-night/colors.xml
similarity index 69%
copy from packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
copy to libs/WindowManager/Shell/res/values-night/colors.xml
index a793680..24b3640 100644
--- a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
+++ b/libs/WindowManager/Shell/res/values-night/colors.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
@@ -12,10 +11,10 @@
   ~ 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
+  ~ limitations under the License.
   -->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_pressed="true">
-        <ripple android:color="#99999999" />
-    </item>
-</selector>
\ No newline at end of file
+
+<resources>
+    <!-- Bubbles -->
+    <color name="bubbles_icon_tint">@color/GM2_grey_200</color>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index cc3bf2a..1674d0b 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -25,4 +25,14 @@
 
     <!-- Background for the various drop targets when handling drag and drop. -->
     <color name="drop_outline_background">#330000FF</color>
+
+    <!-- Bubbles -->
+    <color name="bubbles_light">#FFFFFF</color>
+    <color name="bubbles_dark">@color/GM2_grey_800</color>
+    <color name="bubbles_icon_tint">@color/GM2_grey_700</color>
+
+    <!-- GM2 colors -->
+    <color name="GM2_grey_200">#E8EAED</color>
+    <color name="GM2_grey_700">#5F6368</color>
+    <color name="GM2_grey_800">#3C4043</color>
 </resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index b87a642..8a60aaf 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -75,4 +75,94 @@
 
     <!-- The amount to inset the drop target regions from the edge of the display -->
     <dimen name="drop_layout_display_margin">16dp</dimen>
+
+    <!-- The menu grid size for bubble menu. -->
+    <dimen name="bubble_grid_item_icon_width">20dp</dimen>
+    <dimen name="bubble_grid_item_icon_height">20dp</dimen>
+    <dimen name="bubble_grid_item_icon_top_margin">12dp</dimen>
+    <dimen name="bubble_grid_item_icon_bottom_margin">4dp</dimen>
+    <dimen name="bubble_grid_item_icon_side_margin">22dp</dimen>
+
+    <!-- How much each bubble is elevated. -->
+    <dimen name="bubble_elevation">1dp</dimen>
+    <!-- How much the bubble flyout text container is elevated. -->
+    <dimen name="bubble_flyout_elevation">4dp</dimen>
+    <!-- How much padding is around the left and right sides of the flyout text. -->
+    <dimen name="bubble_flyout_padding_x">12dp</dimen>
+    <!-- How much padding is around the top and bottom of the flyout text. -->
+    <dimen name="bubble_flyout_padding_y">10dp</dimen>
+    <!-- Size of the triangle that points from the flyout to the bubble stack. -->
+    <dimen name="bubble_flyout_pointer_size">6dp</dimen>
+    <!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
+    <dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
+    <!-- How much space to leave between the flyout text and the avatar displayed in the flyout. -->
+    <dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
+    <!-- Padding between status bar and bubbles when displayed in expanded state -->
+    <dimen name="bubble_padding_top">16dp</dimen>
+    <!-- Size of individual bubbles. -->
+    <dimen name="individual_bubble_size">60dp</dimen>
+    <!-- Size of bubble bitmap. -->
+    <dimen name="bubble_bitmap_size">52dp</dimen>
+    <!-- Size of bubble icon bitmap. -->
+    <dimen name="bubble_overflow_icon_bitmap_size">24dp</dimen>
+    <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
+    <dimen name="bubble_touch_padding">12dp</dimen>
+    <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
+    <dimen name="bubble_dismiss_encircle_size">52dp</dimen>
+    <!-- Padding around the view displayed when the bubble is expanded -->
+    <dimen name="bubble_expanded_view_padding">4dp</dimen>
+    <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
+         a slight touch slop around the expanded view. -->
+    <dimen name="bubble_expanded_view_slop">8dp</dimen>
+    <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
+    <dimen name="bubble_expanded_default_height">180dp</dimen>
+    <!-- Default height of bubble overflow -->
+    <dimen name="bubble_overflow_height">480dp</dimen>
+    <!-- Bubble overflow padding when there are no bubbles  -->
+    <dimen name="bubble_overflow_empty_state_padding">16dp</dimen>
+    <!-- Padding of container for overflow bubbles -->
+    <dimen name="bubble_overflow_padding">15dp</dimen>
+    <!-- Padding of label for bubble overflow view -->
+    <dimen name="bubble_overflow_text_padding">7dp</dimen>
+    <!-- Height of bubble overflow empty state illustration -->
+    <dimen name="bubble_empty_overflow_image_height">200dp</dimen>
+    <!-- Padding of bubble overflow empty state subtitle -->
+    <dimen name="bubble_empty_overflow_subtitle_padding">50dp</dimen>
+    <!-- Height of the triangle that points to the expanded bubble -->
+    <dimen name="bubble_pointer_height">8dp</dimen>
+    <!-- Width of the triangle that points to the expanded bubble -->
+    <dimen name="bubble_pointer_width">12dp</dimen>
+    <!-- Extra padding around the dismiss target for bubbles -->
+    <dimen name="bubble_dismiss_slop">16dp</dimen>
+    <!-- Height of button allowing users to adjust settings for bubbles. -->
+    <dimen name="bubble_manage_button_height">48dp</dimen>
+    <!-- Max width of the message bubble-->
+    <dimen name="bubble_message_max_width">144dp</dimen>
+    <!-- Min width of the message bubble -->
+    <dimen name="bubble_message_min_width">32dp</dimen>
+    <!-- Interior padding of the message bubble -->
+    <dimen name="bubble_message_padding">4dp</dimen>
+    <!-- Offset between bubbles in their stacked position. -->
+    <dimen name="bubble_stack_offset">10dp</dimen>
+    <!-- Offset between stack y and animation y for bubble swap. -->
+    <dimen name="bubble_swap_animation_offset">15dp</dimen>
+    <!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
+    <dimen name="bubble_stack_offscreen">9dp</dimen>
+    <!-- How far down the screen the stack starts. -->
+    <dimen name="bubble_stack_starting_offset_y">120dp</dimen>
+    <!-- Space between the pointer triangle and the bubble expanded view -->
+    <dimen name="bubble_pointer_margin">8dp</dimen>
+    <!-- Padding applied to the bubble dismiss target. Touches in this padding cause the bubbles to
+         snap to the dismiss target. -->
+    <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
+    <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+    <dimen name="bubble_manage_menu_elevation">4dp</dimen>
+
+    <!-- Bubbles user education views -->
+    <dimen name="bubbles_manage_education_width">160dp</dimen>
+    <!-- The inset from the top bound of the manage button to place the user education. -->
+    <dimen name="bubbles_manage_education_top_inset">65dp</dimen>
+    <!-- Size of padding for the user education cling, this should at minimum be larger than
+        individual_bubble_size + some padding. -->
+    <dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/ids.xml b/libs/WindowManager/Shell/res/values/ids.xml
index fb89238..434a000 100644
--- a/libs/WindowManager/Shell/res/values/ids.xml
+++ b/libs/WindowManager/Shell/res/values/ids.xml
@@ -23,4 +23,21 @@
     <item type="id" name="action_move_tl_50" />
     <item type="id" name="action_move_tl_30" />
     <item type="id" name="action_move_rb_full" />
+
+    <!-- For saving PhysicsAnimationLayout animations/animators as view tags. -->
+    <item type="id" name="translation_x_dynamicanimation_tag"/>
+    <item type="id" name="translation_y_dynamicanimation_tag"/>
+    <item type="id" name="translation_z_dynamicanimation_tag"/>
+    <item type="id" name="alpha_dynamicanimation_tag"/>
+    <item type="id" name="scale_x_dynamicanimation_tag"/>
+    <item type="id" name="scale_y_dynamicanimation_tag"/>
+    <item type="id" name="physics_animator_tag"/>
+    <item type="id" name="target_animator_tag" />
+    <item type="id" name="reorder_animator_tag"/>
+
+    <!-- Accessibility actions for bubbles. -->
+    <item type="id" name="action_move_top_left"/>
+    <item type="id" name="action_move_top_right"/>
+    <item type="id" name="action_move_bottom_left"/>
+    <item type="id" name="action_move_bottom_right"/>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/integers.xml b/libs/WindowManager/Shell/res/values/integers.xml
new file mode 100644
index 0000000..583bf33
--- /dev/null
+++ b/libs/WindowManager/Shell/res/values/integers.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<resources>
+    <!-- Maximum number of bubbles to render and animate at one time. While the animations used are
+         lightweight translation animations, this number can be reduced on lower end devices if any
+         performance issues arise. -->
+    <integer name="bubbles_max_rendered">5</integer>
+    <!-- Number of columns in bubble overflow. -->
+    <integer name="bubbles_overflow_columns">4</integer>
+    <!-- Maximum number of bubbles we allow in overflow before we dismiss the oldest one. -->
+    <integer name="bubbles_max_overflow">16</integer>
+</resources>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index da5965d..30ef72c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -97,4 +97,53 @@
     <string name="accessibility_action_start_one_handed">Start one-handed mode</string>
     <!-- Accessibility description for stop one-handed mode [CHAR LIMIT=NONE] -->
     <string name="accessibility_action_stop_one_handed">Exit one-handed mode</string>
+
+    <!-- Text used for content description of settings button in the header of expanded bubble
+         view. [CHAR_LIMIT=NONE] -->
+    <string name="bubbles_settings_button_description">Settings for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubbles</string>
+    <!-- Content description for button that shows bubble overflow on click [CHAR LIMIT=NONE] -->
+    <string name="bubble_overflow_button_content_description">Overflow</string>
+    <!-- Action to add overflow bubble back to stack. [CHAR LIMIT=NONE] -->
+    <string name="bubble_accessibility_action_add_back">Add back to stack</string>
+    <!-- Content description when a bubble is focused. [CHAR LIMIT=NONE] -->
+    <string name="bubble_content_description_single"><xliff:g id="notification_title" example="some title">%1$s</xliff:g> from <xliff:g id="app_name" example="YouTube">%2$s</xliff:g></string>
+    <!-- Content description when the stack of bubbles is focused. [CHAR LIMIT=NONE] -->
+    <string name="bubble_content_description_stack"><xliff:g id="notification_title" example="some title">%1$s</xliff:g> from <xliff:g id="app_name" example="YouTube">%2$s</xliff:g> and <xliff:g id="bubble_count" example="4">%3$d</xliff:g> more</string>
+    <!-- Action in accessibility menu to move the stack of bubbles to the top left of the screen. [CHAR LIMIT=30] -->
+    <string name="bubble_accessibility_action_move_top_left">Move top left</string>
+    <!-- Action in accessibility menu to move the stack of bubbles to the top right of the screen. [CHAR LIMIT=30] -->
+    <string name="bubble_accessibility_action_move_top_right">Move top right</string>
+    <!-- Action in accessibility menu to move the stack of bubbles to the bottom left of the screen. [CHAR LIMIT=30]-->
+    <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
+    <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
+    <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
+    <!-- Label for the button that takes the user to the notification settings for the given app. -->
+    <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
+    <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
+    <string name="bubble_dismiss_text">Dismiss bubble</string>
+    <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]-->
+    <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string>
+    <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
+    <string name="bubbles_user_education_title">Chat using bubbles</string>
+    <!-- Descriptive text for the bubble feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
+    <string name="bubbles_user_education_description">New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it.</string>
+    <!-- Title text for the bubble "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
+    <string name="bubbles_user_education_manage_title">Control bubbles anytime</string>
+    <!-- Descriptive text for the bubble "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
+    <string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
+    <!-- Button text for dismissing the bubble "manage" button tool tip  [CHAR LIMIT=20]-->
+    <string name="bubbles_user_education_got_it">Got it</string>
+    <!-- [CHAR LIMIT=NONE] Empty overflow title -->
+    <string name="bubble_overflow_empty_title">No recent bubbles</string>
+    <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
+    <string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
+
+    <!-- [CHAR LIMIT=100] Notification Importance title -->
+    <string name="notification_bubble_title">Bubble</string>
+
+    <!-- The text for the manage bubbles link. [CHAR LIMIT=NONE] -->
+    <string name="manage_bubbles_text">Manage</string>
+
+    <!-- Content description to tell the user a bubble has been dismissed. -->
+    <string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
index 0a2cfbf..59a765d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/TaskView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
@@ -30,26 +30,23 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.util.CloseGuard;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
-import com.android.wm.shell.ShellTaskOrganizer;
-
-import dalvik.system.CloseGuard;
-
 import java.io.PrintWriter;
 import java.util.concurrent.Executor;
 
 /**
  * View that can display a task.
  */
-// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
 public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
         ShellTaskOrganizer.TaskListener {
 
+    /** Callback for listening task state. */
     public interface Listener {
         /** Called when the container is ready for launching activities. */
         default void onInitialized() {}
@@ -70,7 +67,7 @@
         default void onBackPressedOnTaskRoot(int taskId) {}
     }
 
-    private final CloseGuard mGuard = CloseGuard.get();
+    private final CloseGuard mGuard = new CloseGuard();
 
     private final ShellTaskOrganizer mTaskOrganizer;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index a3b720c..8aca01d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -56,4 +56,10 @@
      * Interpolator to be used when animating a move based on a click. Pair with enough duration.
      */
     public static final Interpolator TOUCH_RESPONSE = new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
+    /**
+     * Interpolator to be used when animating a panel closing.
+     */
+    public static final Interpolator PANEL_CLOSE_ACCELERATED =
+            new PathInterpolator(0.3f, 0, 0.5f, 1);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index 8bcffc8..4d06c03 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 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.
@@ -13,7 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
+
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -28,14 +31,11 @@
 import android.widget.ImageView;
 
 import com.android.launcher3.icons.DotRenderer;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
 
 import java.util.EnumSet;
 
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
 /**
  * View that displays an adaptive icon with an app-badge and a dot.
  *
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 09d9e03..93ed395 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.os.AsyncTask.Status.FINISHED;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 598a604..05acb55 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 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.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -22,8 +22,8 @@
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
@@ -59,8 +59,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.dagger.BubbleModule;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -165,7 +163,7 @@
     private boolean mIsStatusBarShade = true;
 
     /**
-     * Injected constructor. See {@link BubbleModule}.
+     * Injected constructor.
      */
     public static BubbleController create(Context context,
             @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
@@ -175,7 +173,7 @@
             WindowManagerShellWrapper windowManagerShellWrapper,
             LauncherApps launcherApps,
             UiEventLogger uiEventLogger,
-            @Main Handler mainHandler,
+            Handler mainHandler,
             ShellTaskOrganizer organizer) {
         BubbleLogger logger = new BubbleLogger(uiEventLogger);
         return new BubbleController(context,
@@ -903,7 +901,7 @@
                 }
                 if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
                     if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
-                        && (!bubble.showInShade()
+                            && (!bubble.showInShade()
                             || reason == DISMISS_NOTIF_CANCEL
                             || reason == DISMISS_GROUP_CANCELLED)) {
                         // The bubble is now gone & the notification is hidden from the shade, so
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 8cacc8f..b6a97e2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.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.
@@ -13,13 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.annotation.NonNull;
 import android.app.PendingIntent;
@@ -33,8 +33,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.Bubbles.DismissReason;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index 2ab9e87..fc565f1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles
+package com.android.wm.shell.bubbles
 
 import android.annotation.SuppressLint
 import android.annotation.UserIdInt
@@ -24,9 +24,9 @@
 import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
 import android.os.UserHandle
 import android.util.Log
-import com.android.systemui.bubbles.storage.BubbleEntity
-import com.android.systemui.bubbles.storage.BubblePersistentRepository
-import com.android.systemui.bubbles.storage.BubbleVolatileRepository
+import com.android.wm.shell.bubbles.storage.BubbleEntity
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.bubbles.storage.BubbleVolatileRepository
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index 3937422..53f4e87 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.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.bubbles;
+package com.android.wm.shell.bubbles;
 
 import android.content.Context;
 import android.provider.Settings;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
index a0d3391..ff68861 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleEntry.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static android.app.Notification.FLAG_BUBBLE;
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index ae3c683..74521c7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 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,17 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.systemui.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_CONTROLLER;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_CONTROLLER;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -54,10 +54,11 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.systemui.R;
-import com.android.systemui.recents.TriangleShape;
-import com.android.systemui.statusbar.AlphaOptimizedButton;
+import com.android.wm.shell.R;
+import com.android.wm.shell.TaskView;
+import com.android.wm.shell.common.AlphaOptimizedButton;
 import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.TriangleShape;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index d8b3250..460e0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.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,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static android.graphics.Paint.ANTI_ALIAS_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-import static com.android.systemui.Interpolators.ALPHA_IN;
-import static com.android.systemui.Interpolators.ALPHA_OUT;
+
+import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
+import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
 
 import android.animation.ArgbEvaluator;
 import android.content.Context;
@@ -47,8 +48,8 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.systemui.R;
-import com.android.systemui.recents.TriangleShape;
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.TriangleShape;
 
 /**
  * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
index 371e849..2d9da21 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleIconFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 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.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,14 +26,13 @@
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.ShadowGenerator;
-import com.android.systemui.R;
+import com.android.wm.shell.R;
 
 /**
  * Factory for creating normalized bubble icons.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
index a24f5c2..3361c4c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleLogger.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.UiEvent;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 297144a..686d2d4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles
+package com.android.wm.shell.bubbles
 
 import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.content.Context
@@ -31,7 +31,7 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.widget.FrameLayout
-import com.android.systemui.R
+import com.android.wm.shell.R
 
 class BubbleOverflow(
     private val context: Context,
@@ -72,7 +72,7 @@
         updateResources()
         expandedView.applyThemeAttrs()
         // Apply inset and new style to fresh icon drawable.
-        overflowBtn.setImageResource(R.drawable.ic_bubble_overflow_button)
+        overflowBtn.setImageResource(R.drawable.bubble_ic_overflow_button)
         updateBtnTheme()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
index bc84173..2759b59 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowActivity.java
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.app.Activity;
 import android.content.Context;
@@ -43,13 +43,12 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
+import com.android.wm.shell.R;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 
-
 /**
  * Activity for showing aged out bubbles.
  * Must be public to be accessible to androidx...AppComponentFactory
@@ -168,8 +167,8 @@
         final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
 
         mEmptyStateImage.setImageDrawable(isNightMode
-                ? res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark)
-                : res.getDrawable(R.drawable.ic_empty_bubble_overflow_light));
+                ? res.getDrawable(R.drawable.bubble_ic_empty_overflow_dark)
+                : res.getDrawable(R.drawable.bubble_ic_empty_overflow_light));
 
         findViewById(android.R.id.content)
                 .setBackgroundColor(isNightMode
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubblePositioner.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index 029caee..eccd009 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import android.content.Context;
 import android.content.res.Configuration;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 69ed5b7..155f342 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 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,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -71,13 +71,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.bubbles.animation.AnimatableScaleMatrix;
-import com.android.systemui.bubbles.animation.ExpandedAnimationController;
-import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
-import com.android.systemui.bubbles.animation.StackAnimationController;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
+import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
+import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
+import com.android.wm.shell.bubbles.animation.StackAnimationController;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
@@ -2661,14 +2661,17 @@
                 .floatValue();
     }
 
+    /** Set the start position of the bubble stack. */
     public void setStackStartPosition(RelativeStackPosition position) {
         mStackAnimationController.setStackStartPosition(position);
     }
 
+    /** @return the position of the bubble stack. */
     public PointF getStackPosition() {
         return mStackAnimationController.getStackPosition();
     }
 
+    /** @return the relative position of the bubble stack. */
     public RelativeStackPosition getRelativeStackPosition() {
         return mStackAnimationController.getRelativeStackPosition();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index a3e6a1e..0b68306 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.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,12 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
-import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
-import static com.android.systemui.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
+import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.annotation.NonNull;
 import android.content.Context;
@@ -42,7 +42,7 @@
 
 import com.android.internal.graphics.ColorUtils;
 import com.android.launcher3.icons.BitmapInfo;
-import com.android.systemui.R;
+import com.android.wm.shell.R;
 
 import java.lang.ref.WeakReference;
 import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
index 5890172..ec900be 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewProvider.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import android.graphics.Bitmap;
 import android.graphics.Path;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 415edb1..79c42d8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
index b3c552d..04b5ad6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DismissView.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.bubbles
+/*
+ * 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.wm.shell.bubbles
 
 import android.content.Context
 import android.graphics.drawable.TransitionDrawable
@@ -9,9 +25,9 @@
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
 import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
-import com.android.systemui.R
-import com.android.wm.shell.common.DismissCircleView
+import com.android.wm.shell.R
 import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.common.DismissCircleView
 
 /*
  * View that handles interactions between DismissCircleView and BubbleStackView.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index 3db07c2..4cc6702 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles
+package com.android.wm.shell.bubbles
 
 import android.content.Context
 import android.graphics.Color
@@ -24,8 +24,8 @@
 import android.widget.LinearLayout
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
-import com.android.systemui.Interpolators
-import com.android.systemui.R
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
 
 /**
  * User education view to highlight the manage button that allows a user to configure the settings
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ObjectWrapper.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ObjectWrapper.java
index f054122..528907f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ObjectWrapper.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import android.os.Binder;
 import android.os.IBinder;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
index b1291a5..b347329 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles
+package com.android.wm.shell.bubbles
 
 import android.graphics.PointF
 import android.os.Handler
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index 216df2e..04c4dfb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles
+package com.android.wm.shell.bubbles
 
 import android.content.Context
 import android.graphics.Color
@@ -23,8 +23,8 @@
 import android.widget.LinearLayout
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
-import com.android.systemui.Interpolators
-import com.android.systemui.R
+import com.android.wm.shell.R
+import com.android.wm.shell.animation.Interpolators
 
 /**
  * User education view to highlight the collapsed stack of bubbles.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java
index 07acb71..2612b81 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/AnimatableScaleMatrix.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import android.graphics.Matrix;
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 5a70401..61fbf81 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -28,10 +28,10 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.bubbles.BubblePositioner;
+import com.android.wm.shell.R;
+import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
 import com.google.android.collect.Sets;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OneTimeEndListener.java
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OneTimeEndListener.java
index 4e0abc8..37355c4 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OneTimeEndListener.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
index 0a596d5..0618d5d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayout.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -35,7 +35,7 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.systemui.R;
+import com.android.wm.shell.R;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
similarity index 99%
rename from packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 43893f2..d7f2e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import android.content.ContentResolver;
 import android.content.res.Resources;
@@ -34,11 +34,11 @@
 import androidx.dynamicanimation.animation.SpringAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.systemui.R;
-import com.android.systemui.bubbles.BadgedImageView;
-import com.android.systemui.bubbles.BubblePositioner;
-import com.android.systemui.bubbles.BubbleStackView;
+import com.android.wm.shell.R;
 import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BadgedImageView;
+import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
index 24768cd..aeba302 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles.storage
+package com.android.wm.shell.bubbles.storage
 
 import android.annotation.DimenRes
 import android.annotation.UserIdInt
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt
index ce0786d..66a75af 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepository.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles.storage
+package com.android.wm.shell.bubbles.storage
 
 import android.content.Context
 import android.util.AtomicFile
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt
index e0a7c78..7f0b165 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepository.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles.storage
+package com.android.wm.shell.bubbles.storage
 
 import android.content.pm.LauncherApps
 import android.os.UserHandle
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.bubbles.ShortcutKey
+import com.android.wm.shell.bubbles.ShortcutKey
 
 private const val CAPACITY = 16
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
rename to libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
index bf163a2..fe72bd3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt
@@ -13,14 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles.storage
+package com.android.wm.shell.bubbles.storage
 
-import com.android.internal.util.FastXmlSerializer
-import org.xmlpull.v1.XmlSerializer
-import java.io.IOException
 import android.util.Xml
+import com.android.internal.util.FastXmlSerializer
 import com.android.internal.util.XmlUtils
 import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlSerializer
+import java.io.IOException
 import java.io.InputStream
 import java.io.OutputStream
 import java.nio.charset.StandardCharsets
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/AlphaOptimizedButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AlphaOptimizedButton.java
new file mode 100644
index 0000000..6f0a61b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/AlphaOptimizedButton.java
@@ -0,0 +1,50 @@
+/*
+ * 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.wm.shell.common;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+/**
+ * A Button which doesn't have overlapping drawing commands
+ *
+ * This is the copy from SystemUI/statusbar.
+ */
+public class AlphaOptimizedButton extends Button {
+    public AlphaOptimizedButton(Context context) {
+        super(context);
+    }
+
+    public AlphaOptimizedButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AlphaOptimizedButton(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TriangleShape.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TriangleShape.java
new file mode 100644
index 0000000..7079190
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TriangleShape.java
@@ -0,0 +1,77 @@
+/*
+ * 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.wm.shell.common;
+
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.drawable.shapes.PathShape;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Wrapper around {@link PathShape}
+ * that creates a shape with a triangular path (pointing up or down).
+ *
+ * This is the copy from SystemUI/recents.
+ */
+public class TriangleShape extends PathShape {
+    private Path mTriangularPath;
+
+    public TriangleShape(Path path, float stdWidth, float stdHeight) {
+        super(path, stdWidth, stdHeight);
+        mTriangularPath = path;
+    }
+
+    public static TriangleShape create(float width, float height, boolean isPointingUp) {
+        Path triangularPath = new Path();
+        if (isPointingUp) {
+            triangularPath.moveTo(0, height);
+            triangularPath.lineTo(width, height);
+            triangularPath.lineTo(width / 2, 0);
+            triangularPath.close();
+        } else {
+            triangularPath.moveTo(0, 0);
+            triangularPath.lineTo(width / 2, height);
+            triangularPath.lineTo(width, 0);
+            triangularPath.close();
+        }
+        return new TriangleShape(triangularPath, width, height);
+    }
+
+    /** Create an arrow TriangleShape that points to the left or the right */
+    public static TriangleShape createHorizontal(
+            float width, float height, boolean isPointingLeft) {
+        Path triangularPath = new Path();
+        if (isPointingLeft) {
+            triangularPath.moveTo(0, height / 2);
+            triangularPath.lineTo(width, height);
+            triangularPath.lineTo(width, 0);
+            triangularPath.close();
+        } else {
+            triangularPath.moveTo(0, height);
+            triangularPath.lineTo(width, height / 2);
+            triangularPath.lineTo(0, 0);
+            triangularPath.close();
+        }
+        return new TriangleShape(triangularPath, width, height);
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        outline.setPath(mTriangularPath);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index bf5b1d8..c77f594 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -16,17 +16,9 @@
 
 package com.android.wm.shell.draganddrop;
 
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
-import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
-import static android.content.Intent.EXTRA_PACKAGE_NAME;
-import static android.content.Intent.EXTRA_SHORTCUT_ID;
-import static android.content.Intent.EXTRA_TASK_ID;
-import static android.content.Intent.EXTRA_USER;
 import static android.view.DragEvent.ACTION_DRAG_ENDED;
 import static android.view.DragEvent.ACTION_DRAG_ENTERED;
 import static android.view.DragEvent.ACTION_DRAG_EXITED;
@@ -42,25 +34,10 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.view.DragEvent;
 import android.view.LayoutInflater;
@@ -76,6 +53,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.SplitScreen;
 
+import java.util.Objects;
 import java.util.Optional;
 
 /**
@@ -91,10 +69,12 @@
     private SplitScreen mSplitScreen;
 
     private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
-    private boolean mIsHandlingDrag;
-    private DragLayout mDragLayout;
     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
 
+    // A count of the number of active drags in progress to ensure that we only hide the window when
+    // all the drag animations have completed
+    private int mActiveDragCount;
+
     public DragAndDropController(Context context, DisplayController displayController) {
         mContext = context;
         mDisplayController = displayController;
@@ -124,26 +104,31 @@
         layoutParams.setFitInsetsTypes(0);
         layoutParams.setTitle("ShellDropTarget");
 
-        FrameLayout dropTarget = (FrameLayout) LayoutInflater.from(context).inflate(
+        FrameLayout rootView = (FrameLayout) LayoutInflater.from(context).inflate(
                 R.layout.global_drop_target, null);
-        dropTarget.setOnDragListener(this);
-        dropTarget.setVisibility(View.INVISIBLE);
-        wm.addView(dropTarget, layoutParams);
-        mDisplayDropTargets.put(displayId, new PerDisplay(displayId, context, wm, dropTarget));
+        rootView.setOnDragListener(this);
+        rootView.setVisibility(View.INVISIBLE);
+        DragLayout dragLayout = new DragLayout(context, mSplitScreen);
+        rootView.addView(dragLayout,
+                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+        wm.addView(rootView, layoutParams);
+
+        mDisplayDropTargets.put(displayId,
+                new PerDisplay(displayId, context, wm, rootView, dragLayout));
     }
 
     @Override
     public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display changed: %d", displayId);
         final PerDisplay pd = mDisplayDropTargets.get(displayId);
-        pd.dropTarget.requestApplyInsets();
+        pd.rootView.requestApplyInsets();
     }
 
     @Override
     public void onDisplayRemoved(int displayId) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Display removed: %d", displayId);
         final PerDisplay pd = mDisplayDropTargets.get(displayId);
-        pd.wm.removeViewImmediate(pd.dropTarget);
+        pd.wm.removeViewImmediate(pd.rootView);
         mDisplayDropTargets.remove(displayId);
     }
 
@@ -158,32 +143,33 @@
         final ClipDescription description = event.getClipDescription();
 
         if (event.getAction() == ACTION_DRAG_STARTED) {
-            final boolean hasValidClipData = description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
-                    || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
-                    || description.hasMimeType(MIMETYPE_APPLICATION_TASK);
-            mIsHandlingDrag = hasValidClipData;
+            final boolean hasValidClipData = event.getClipData().getItemCount() > 0
+                    && (description.hasMimeType(MIMETYPE_APPLICATION_ACTIVITY)
+                            || description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT)
+                            || description.hasMimeType(MIMETYPE_APPLICATION_TASK));
+            pd.isHandlingDrag = hasValidClipData;
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
-                    "Clip description: handlingDrag=%b mimeTypes=%s",
-                    mIsHandlingDrag, getMimeTypes(description));
+                    "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s",
+                    pd.isHandlingDrag, event.getClipData().getItemCount(),
+                    getMimeTypes(description));
         }
 
-        if (!mIsHandlingDrag) {
+        if (!pd.isHandlingDrag) {
             return false;
         }
 
         switch (event.getAction()) {
             case ACTION_DRAG_STARTED:
-                mDragLayout = new DragLayout(pd.context,
-                        mDisplayController.getDisplayLayout(displayId), mSplitScreen);
-                pd.dropTarget.addView(mDragLayout,
-                        new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+                mActiveDragCount++;
+                pd.dragLayout.prepare(mDisplayController.getDisplayLayout(displayId),
+                        event.getClipData());
                 setDropTargetWindowVisibility(pd, View.VISIBLE);
                 break;
             case ACTION_DRAG_ENTERED:
-                mDragLayout.show(event);
+                pd.dragLayout.show();
                 break;
             case ACTION_DRAG_LOCATION:
-                mDragLayout.update(event);
+                pd.dragLayout.update(event);
                 break;
             case ACTION_DROP: {
                 return handleDrop(event, pd);
@@ -191,20 +177,22 @@
             case ACTION_DRAG_EXITED: {
                 // Either one of DROP or EXITED will happen, and when EXITED we won't consume
                 // the drag surface
-                mDragLayout.hide(event, null);
+                pd.dragLayout.hide(event, null);
                 break;
             }
             case ACTION_DRAG_ENDED:
                 // TODO(b/169894807): Ensure sure it's not possible to get ENDED without DROP
                 // or EXITED
-                if (!mDragLayout.hasDropped()) {
-                    final View dragLayout = mDragLayout;
-                    mDragLayout.hide(event, () -> {
-                        setDropTargetWindowVisibility(pd, View.INVISIBLE);
-                        pd.dropTarget.removeView(dragLayout);
+                if (!pd.dragLayout.hasDropped()) {
+                    mActiveDragCount--;
+                    pd.dragLayout.hide(event, () -> {
+                        if (mActiveDragCount == 0) {
+                            // Hide the window if another drag hasn't been started while animating
+                            // the drag-end
+                            setDropTargetWindowVisibility(pd, View.INVISIBLE);
+                        }
                     });
                 }
-                mDragLayout = null;
                 break;
         }
         return true;
@@ -214,52 +202,14 @@
      * Handles dropping on the drop target.
      */
     private boolean handleDrop(DragEvent event, PerDisplay pd) {
-        final ClipData data = event.getClipData();
-        final ClipDescription description = event.getClipDescription();
         final SurfaceControl dragSurface = event.getDragSurface();
-        final View dragLayout = mDragLayout;
-        final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
-        final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
-        return mDragLayout.drop(event, dragSurface, (dropTargetBounds) -> {
-            if (dropTargetBounds != null && data.getItemCount() > 0) {
-                final Intent intent = data.getItemAt(0).getIntent();
-                // TODO(b/169894807): Properly handle the drop, for now just launch it
-                if (isTask) {
-                    int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
-                    try {
-                        ActivityTaskManager.getService().startActivityFromRecents(
-                                taskId, null);
-                    } catch (RemoteException e) {
-                        Slog.e(TAG, "Failed to launch task", e);
-                    }
-                } else if (isShortcut) {
-                    try {
-                        Bundle opts = intent.hasExtra(EXTRA_ACTIVITY_OPTIONS)
-                                ? intent.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
-                                : null;
-                        LauncherApps launcherApps =
-                                mContext.getSystemService(LauncherApps.class);
-                        launcherApps.startShortcut(
-                                intent.getStringExtra(EXTRA_PACKAGE_NAME),
-                                intent.getStringExtra(EXTRA_SHORTCUT_ID),
-                                null /* sourceBounds */, opts,
-                                intent.getParcelableExtra(EXTRA_USER));
-                    } catch (ActivityNotFoundException e) {
-                        Slog.e(TAG, "Failed to launch shortcut", e);
-                    }
-                } else {
-                    PendingIntent pi = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
-                    try {
-                        pi.send();
-                    } catch (PendingIntent.CanceledException e) {
-                        Slog.e(TAG, "Failed to launch activity", e);
-                    }
-                }
+        mActiveDragCount--;
+        return pd.dragLayout.drop(event, dragSurface, () -> {
+            if (mActiveDragCount == 0) {
+                // Hide the window if another drag hasn't been started while animating the drop
+                setDropTargetWindowVisibility(pd, View.INVISIBLE);
             }
 
-            setDropTargetWindowVisibility(pd, View.INVISIBLE);
-            pd.dropTarget.removeView(dragLayout);
-
             // Clean up the drag surface
             mTransaction.reparent(dragSurface, null);
             mTransaction.apply();
@@ -270,9 +220,9 @@
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                 "Set drop target window visibility: displayId=%d visibility=%d",
                 pd.displayId, visibility);
-        pd.dropTarget.setVisibility(visibility);
+        pd.rootView.setVisibility(visibility);
         if (visibility == View.VISIBLE) {
-            pd.dropTarget.requestApplyInsets();
+            pd.rootView.requestApplyInsets();
         }
     }
 
@@ -291,13 +241,17 @@
         final int displayId;
         final Context context;
         final WindowManager wm;
-        final FrameLayout dropTarget;
+        final FrameLayout rootView;
+        final DragLayout dragLayout;
 
-        PerDisplay(int dispId, Context c, WindowManager w, FrameLayout l) {
+        boolean isHandlingDrag;
+
+        PerDisplay(int dispId, Context c, WindowManager w, FrameLayout rv, DragLayout dl) {
             displayId = dispId;
             context = c;
             wm = w;
-            dropTarget = l;
+            rootView = rv;
+            dragLayout = dl;
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
new file mode 100644
index 0000000..25890bc
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -0,0 +1,409 @@
+/*
+ * 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.wm.shell.draganddrop;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS;
+import static android.content.ClipDescription.EXTRA_PENDING_INTENT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_SHORTCUT_ID;
+import static android.content.Intent.EXTRA_TASK_ID;
+import static android.content.Intent.EXTRA_USER;
+
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.IActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.WindowConfiguration;
+import android.content.ActivityNotFoundException;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.LauncherApps;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The policy for handling drag and drop operations to shell.
+ */
+public class DragAndDropPolicy {
+
+    private static final String TAG = DragAndDropPolicy.class.getSimpleName();
+
+    private final Context mContext;
+    private final IActivityTaskManager mIActivityTaskManager;
+    private final Starter mStarter;
+    private final SplitScreen mSplitScreen;
+    private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
+
+    private DragSession mSession;
+
+    public DragAndDropPolicy(Context context, SplitScreen splitScreen) {
+        this(context, ActivityTaskManager.getService(), splitScreen,
+                new DefaultStarter(context, splitScreen));
+    }
+
+    @VisibleForTesting
+    DragAndDropPolicy(Context context, IActivityTaskManager activityTaskManager,
+            SplitScreen splitScreen, Starter starter) {
+        mContext = context;
+        mIActivityTaskManager = activityTaskManager;
+        mSplitScreen = splitScreen;
+        mStarter = starter;
+    }
+
+    /**
+     * Starts a new drag session with the given initial drag data.
+     */
+    void start(DisplayLayout displayLayout, ClipData data) {
+        mSession = new DragSession(mContext, mIActivityTaskManager, displayLayout, data);
+        // TODO(b/169894807): Also update the session data with task stack changes
+        mSession.update();
+    }
+
+    /**
+     * Returns the target's regions based on the current state of the device and display.
+     */
+    @NonNull
+    ArrayList<Target> getTargets(Insets insets) {
+        mTargets.clear();
+        if (mSession == null) {
+            // Return early if this isn't an app drag
+            return mTargets;
+        }
+
+        final int w = mSession.displayLayout.width();
+        final int h = mSession.displayLayout.height();
+        final int iw = w - insets.left - insets.right;
+        final int ih = h - insets.top - insets.bottom;
+        final int l = insets.left;
+        final int t = insets.top;
+        final boolean isVerticalSplit = mSession.isPhone && !mSession.displayLayout.isLandscape();
+        if (mSession.dragItemSupportsSplitscreen
+                && mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD
+                && mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN
+                && mSession.runningTaskIsResizeable) {
+            // Allow splitting when there is a fullscreen standard activity running
+            if (isVerticalSplit) {
+                // TODO(b/169894807): For now, only allow splitting to the right/bottom until we
+                //                    have split pairs
+                mTargets.add(new Target(TYPE_FULLSCREEN,
+                        new Rect(0, 0, w, h / 2),
+                        new Rect(l, t, l + iw, t + ih),
+                        new Rect(0, 0, w, h)));
+                mTargets.add(new Target(TYPE_SPLIT_BOTTOM,
+                        new Rect(0, h / 2, w, h),
+                        new Rect(l, t + ih / 2, l + iw, t + ih),
+                        new Rect(0, h / 2, w, h)));
+            } else {
+                mTargets.add(new Target(TYPE_FULLSCREEN,
+                        new Rect(0, 0, w / 2, h),
+                        new Rect(l, t, l + iw, t + ih),
+                        new Rect(0, 0, w, h)));
+                mTargets.add(new Target(TYPE_SPLIT_RIGHT,
+                        new Rect(w / 2, 0, w, h),
+                        new Rect(l + iw / 2, t, l + iw, t + ih),
+                        new Rect(w / 2, 0, w, h)));
+            }
+        } else if (mSession.dragItemSupportsSplitscreen
+                && mSplitScreen != null
+                && mSplitScreen.isDividerVisible()) {
+            // Already split, allow replacing existing split task
+            // TODO(b/169894807): For now, only allow replacing the non-primary task until we have
+            //                    split pairs
+            final Rect secondarySplitRawBounds =
+                    mSplitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds();
+            final Rect secondarySplitBounds = new Rect(secondarySplitRawBounds);
+            secondarySplitBounds.intersect(new Rect(l, t, l + iw, t + ih));
+            if (isVerticalSplit) {
+                mTargets.add(new Target(TYPE_FULLSCREEN,
+                        new Rect(0, 0, w, secondarySplitRawBounds.top),
+                        new Rect(l, t, l + iw, t + ih),
+                        new Rect(0, 0, w, secondarySplitRawBounds.top)));
+            } else {
+                mTargets.add(new Target(TYPE_FULLSCREEN,
+                        new Rect(0, 0, secondarySplitRawBounds.left, h),
+                        new Rect(l, t, l + iw, t + ih),
+                        new Rect(0, 0, w, h)));
+            }
+            mTargets.add(new Target(isVerticalSplit ? TYPE_SPLIT_BOTTOM : TYPE_SPLIT_RIGHT,
+                    new Rect(secondarySplitBounds),
+                    new Rect(secondarySplitBounds),
+                    new Rect(secondarySplitBounds)));
+        } else {
+            // Otherwise only show the fullscreen target
+            mTargets.add(new Target(TYPE_FULLSCREEN,
+                    new Rect(0, 0, w, h),
+                    new Rect(l, t, l + iw, t + ih),
+                    new Rect(0, 0, w, h)));
+        }
+        return mTargets;
+    }
+
+    /**
+     * Returns the target at the given position based on the targets previously calculated.
+     */
+    @Nullable
+    Target getTargetAtLocation(int x, int y) {
+        for (int i = mTargets.size() - 1; i >= 0; i--) {
+            DragAndDropPolicy.Target t = mTargets.get(i);
+            if (t.hitRegion.contains(x, y)) {
+                return t;
+            }
+        }
+        return null;
+    }
+
+    @VisibleForTesting
+    void handleDrop(Target target, ClipData data) {
+        if (target == null || !mTargets.contains(target)) {
+            return;
+        }
+
+        final ClipDescription description = data.getDescription();
+        final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
+        final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
+        final Intent dragData = mSession.dragData;
+
+        if (target.type == TYPE_FULLSCREEN) {
+            if (mSplitScreen != null && mSplitScreen.isDividerVisible()) {
+                // If in split, remove split and launch fullscreen
+                mStarter.exitSplitScreen(mSession.runningTaskId);
+            } else {
+                // Not in split, fall through to launch
+            }
+        } else {
+            if (mSplitScreen != null && mSplitScreen.isDividerVisible()) {
+                // Split is already visible, just replace the task
+                // TODO(b/169894807): Since we only allow replacing the non-primary target above
+                //                    just fall through and start the activity
+            } else {
+                // Not in split, enter split now
+                mStarter.enterSplitScreen(mSession.runningTaskId,
+                        target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP);
+            }
+        }
+
+        Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
+                ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
+                : null;
+        if (isTask) {
+            mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
+        } else if (isShortcut) {
+            mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME),
+                    dragData.getStringExtra(EXTRA_SHORTCUT_ID),
+                    opts, dragData.getParcelableExtra(EXTRA_USER));
+        } else {
+            mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
+        }
+    }
+
+    /**
+     * Per-drag session data.
+     */
+    private static class DragSession {
+        private final Context mContext;
+        private final IActivityTaskManager mIActivityTaskManager;
+        private final ClipData mInitialDragData;
+
+        final DisplayLayout displayLayout;
+        Intent dragData;
+        int runningTaskId;
+        @WindowConfiguration.WindowingMode
+        int runningTaskWinMode = WINDOWING_MODE_UNDEFINED;
+        @WindowConfiguration.ActivityType
+        int runningTaskActType = ACTIVITY_TYPE_STANDARD;
+        boolean runningTaskIsResizeable;
+        boolean dragItemSupportsSplitscreen;
+        boolean isPhone;
+
+        DragSession(Context context, IActivityTaskManager activityTaskManager,
+                DisplayLayout dispLayout, ClipData data) {
+            mContext = context;
+            mIActivityTaskManager = activityTaskManager;
+            mInitialDragData = data;
+            displayLayout = dispLayout;
+        }
+
+        /**
+         * Updates the session data based on the current state of the system.
+         */
+        void update() {
+            final ClipDescription description = mInitialDragData.getDescription();
+            try {
+                List<ActivityManager.RunningTaskInfo> tasks =
+                        mIActivityTaskManager.getFilteredTasks(1,
+                                false /* filterOnlyVisibleRecents */);
+                if (!tasks.isEmpty()) {
+                    final ActivityManager.RunningTaskInfo task = tasks.get(0);
+                    runningTaskWinMode = task.getWindowingMode();
+                    runningTaskActType = task.getActivityType();
+                    runningTaskId = task.taskId;
+                    runningTaskIsResizeable = task.isResizeable;
+                }
+            } catch (RemoteException e) {
+                // Fall through
+            }
+
+            final ActivityInfo info = mInitialDragData.getItemAt(0).getActivityInfo();
+            dragItemSupportsSplitscreen = info == null
+                    || ActivityInfo.isResizeableMode(info.resizeMode);
+            isPhone = mContext.getResources().getConfiguration().smallestScreenWidthDp < 600;
+            dragData = mInitialDragData.getItemAt(0).getIntent();
+        }
+    }
+
+    /**
+     * Interface for actually committing the task launches.
+     */
+    @VisibleForTesting
+    interface Starter {
+        void startTask(int taskId, Bundle activityOptions);
+        void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
+                UserHandle user);
+        void startIntent(PendingIntent intent, Bundle activityOptions);
+        void enterSplitScreen(int taskId, boolean leftOrTop);
+        void exitSplitScreen(int taskId);
+    }
+
+    /**
+     * Default implementation of the starter which calls through the system services to launch the
+     * tasks.
+     */
+    private static class DefaultStarter implements Starter {
+        private final Context mContext;
+        private final SplitScreen mSplitScreen;
+
+        public DefaultStarter(Context context, SplitScreen splitScreen) {
+            mContext = context;
+            mSplitScreen = splitScreen;
+        }
+
+        @Override
+        public void startTask(int taskId, Bundle activityOptions) {
+            try {
+                ActivityTaskManager.getService().startActivityFromRecents(taskId, null);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to launch task", e);
+            }
+        }
+
+        @Override
+        public void startShortcut(String packageName, String shortcutId, Bundle activityOptions,
+                UserHandle user) {
+            try {
+                LauncherApps launcherApps =
+                        mContext.getSystemService(LauncherApps.class);
+                launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
+                        activityOptions, user);
+            } catch (ActivityNotFoundException e) {
+                Slog.e(TAG, "Failed to launch shortcut", e);
+            }
+        }
+
+        @Override
+        public void startIntent(PendingIntent intent, Bundle activityOptions) {
+            try {
+                intent.send(null, 0, null, null, null, null, activityOptions);
+            } catch (PendingIntent.CanceledException e) {
+                Slog.e(TAG, "Failed to launch activity", e);
+            }
+        }
+
+        @Override
+        public void enterSplitScreen(int taskId, boolean leftOrTop) {
+            mSplitScreen.splitPrimaryTask();
+        }
+
+        @Override
+        public void exitSplitScreen(int taskId) {
+            mSplitScreen.dismissSplitToPrimaryTask();
+        }
+    }
+
+    /**
+     * Represents a drop target.
+     */
+    static class Target {
+        static final int TYPE_FULLSCREEN = 0;
+        static final int TYPE_SPLIT_LEFT = 1;
+        static final int TYPE_SPLIT_TOP = 2;
+        static final int TYPE_SPLIT_RIGHT = 3;
+        static final int TYPE_SPLIT_BOTTOM = 4;
+        @IntDef(value = {
+                TYPE_FULLSCREEN,
+                TYPE_SPLIT_LEFT,
+                TYPE_SPLIT_TOP,
+                TYPE_SPLIT_RIGHT,
+                TYPE_SPLIT_BOTTOM
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface Type{}
+
+        final @Type int type;
+
+        // The actual hit region for this region
+        final Rect hitRegion;
+        // The approximate visual region for where the task will start
+        final Rect drawRegion;
+        // The
+        final Rect dropTargetBounds;
+
+        public Target(@Type int t, Rect hit, Rect draw, Rect drop) {
+            type = t;
+            hitRegion = hit;
+            drawRegion = draw;
+            dropTargetBounds = drop;
+        }
+
+        @Override
+        public String toString() {
+            return "Target {hit=" + hitRegion + " drop=" + dropTargetBounds + "}";
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index b70036b..fa857cdd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -24,6 +24,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.content.ClipData;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Insets;
@@ -52,20 +53,19 @@
  */
 public class DragLayout extends View {
 
-    private final DisplayLayout mDisplayLayout;
-    private final SplitScreen mSplitScreen;
+    private final DragAndDropPolicy mPolicy;
 
-    private final ArrayList<Target> mTargets = new ArrayList<>();
-    private Target mCurrentTarget = null;
+    private DragAndDropPolicy.Target mCurrentTarget = null;
     private DropOutlineDrawable mDropOutline;
     private int mDisplayMargin;
     private Insets mInsets = Insets.NONE;
+
+    private boolean mIsShowing;
     private boolean mHasDropped;
 
-    public DragLayout(Context context, DisplayLayout displayLayout, SplitScreen splitscreen) {
+    public DragLayout(Context context, SplitScreen splitscreen) {
         super(context);
-        mDisplayLayout = displayLayout;
-        mSplitScreen = splitscreen;
+        mPolicy = new DragAndDropPolicy(context, splitscreen);
         mDisplayMargin = context.getResources().getDimensionPixelSize(
                 R.dimen.drop_layout_display_margin);
         mDropOutline = new DropOutlineDrawable(context);
@@ -76,7 +76,7 @@
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout());
-        calculateDropTargets();
+        recomputeDropTargets();
         return super.onApplyWindowInsets(insets);
     }
 
@@ -100,66 +100,41 @@
         return mHasDropped;
     }
 
-    public void show(DragEvent event) {
-        calculateDropTargets();
+    public void prepare(DisplayLayout displayLayout, ClipData initialData) {
+        mPolicy.start(displayLayout, initialData);
         mHasDropped = false;
+        mCurrentTarget = null;
     }
 
-    private void calculateDropTargets() {
-        // Calculate all the regions based on split and landscape portrait
-        // TODO: Filter based on clip data
-        final float SIDE_MARGIN_PCT = 0.3f;
-        final int w = mDisplayLayout.width();
-        final int h = mDisplayLayout.height();
-        final int iw = w - mInsets.left - mInsets.right;
-        final int ih = h - mInsets.top - mInsets.bottom;
-        final int l = mInsets.left;
-        final int t = mInsets.top;
-        final int r = mInsets.right;
-        final int b = mInsets.bottom;
-        mTargets.clear();
-
-        // Left split
-        addTarget(new Target(
-                new Rect(0, 0,
-                        (int) (w * SIDE_MARGIN_PCT), h),
-                new Rect(l + mDisplayMargin, t + mDisplayMargin,
-                        l + iw / 2 - mDisplayMargin, t + ih - mDisplayMargin),
-                new Rect(0, 0, w / 2, h)));
-
-        // Fullscreen
-        addTarget(new Target(
-                new Rect((int) (w * SIDE_MARGIN_PCT), 0,
-                        w - (int) (w * SIDE_MARGIN_PCT), h),
-                new Rect(l + mDisplayMargin, t + mDisplayMargin,
-                        l + iw - mDisplayMargin, t + ih - mDisplayMargin),
-                new Rect(0, 0, w, h)));
-
-        // Right split
-        addTarget(new Target(
-                new Rect(w - (int) (w * SIDE_MARGIN_PCT), 0,
-                        w, h),
-                new Rect(l + iw / 2 + mDisplayMargin, t + mDisplayMargin,
-                        l + iw - mDisplayMargin, t + ih - mDisplayMargin),
-                new Rect(w / 2, 0, w, h)));
+    public void show() {
+        mIsShowing = true;
+        recomputeDropTargets();
     }
 
-    private void addTarget(Target t) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Add target: %s", t);
-        mTargets.add(t);
+    /**
+     * Recalculates the drop targets based on the current policy.
+     */
+    private void recomputeDropTargets() {
+        if (!mIsShowing) {
+            return;
+        }
+        final ArrayList<DragAndDropPolicy.Target> targets = mPolicy.getTargets(mInsets);
+        for (int i = 0; i < targets.size(); i++) {
+            final DragAndDropPolicy.Target target = targets.get(i);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Add target: %s", target);
+            // Inset the draw region by a little bit
+            target.drawRegion.inset(mDisplayMargin, mDisplayMargin);
+        }
     }
 
+    /**
+     * Updates the visible drop target as the user drags.
+     */
     public void update(DragEvent event) {
         // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
         // visibility of the current region
-        Target target = null;
-        for (int i = mTargets.size() - 1; i >= 0; i--) {
-            Target t = mTargets.get(i);
-            if (t.hitRegion.contains((int) event.getX(), (int) event.getY())) {
-                target = t;
-                break;
-            }
-        }
+        DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
+                (int) event.getX(), (int) event.getY());
         if (target != null && mCurrentTarget != target) {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
             Interpolator boundsInterpolator = FAST_OUT_SLOW_IN;
@@ -175,7 +150,11 @@
         }
     }
 
+    /**
+     * Hides the drag layout and animates out the visible drop targets.
+     */
     public void hide(DragEvent event, Runnable hideCompleteCallback) {
+        mIsShowing = false;
         ObjectAnimator alphaAnimator = mDropOutline.startVisibilityAnimation(false, LINEAR);
         ObjectAnimator boundsAnimator = null;
         if (mCurrentTarget != null) {
@@ -199,50 +178,19 @@
         mCurrentTarget = null;
     }
 
+    /**
+     * Handles the drop onto a target and animates out the visible drop targets.
+     */
     public boolean drop(DragEvent event, SurfaceControl dragSurface,
-            Consumer<Rect> dropCompleteCallback) {
+            Runnable dropCompleteCallback) {
+        final boolean handledDrop = mCurrentTarget != null;
         mHasDropped = true;
+
+        // Process the drop
+        mPolicy.handleDrop(mCurrentTarget, event.getClipData());
+
         // TODO(b/169894807): Coordinate with dragSurface
-        final Rect dropRegion = mCurrentTarget != null
-                ? mCurrentTarget.dropTargetBounds
-                : null;
-
-        ObjectAnimator alphaAnimator = mDropOutline.startVisibilityAnimation(false, LINEAR);
-        ObjectAnimator boundsAnimator = null;
-        if (dropRegion != null) {
-            Rect finalBounds = new Rect(mCurrentTarget.drawRegion);
-            finalBounds.inset(mDisplayMargin, mDisplayMargin);
-            mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN);
-        }
-
-        if (dropCompleteCallback != null) {
-            ObjectAnimator lastAnim = boundsAnimator != null
-                    ? boundsAnimator
-                    : alphaAnimator;
-            lastAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    dropCompleteCallback.accept(dropRegion);
-                }
-            });
-        }
-        return dropRegion != null;
-    }
-
-    private static class Target {
-        final Rect hitRegion;
-        final Rect drawRegion;
-        final Rect dropTargetBounds;
-
-        public Target(Rect hit, Rect draw, Rect drop) {
-            hitRegion = hit;
-            drawRegion = draw;
-            dropTargetBounds = drop;
-        }
-
-        @Override
-        public String toString() {
-            return "Target {hit=" + hitRegion + " drop=" + dropTargetBounds + "}";
-        }
+        hide(event, dropCompleteCallback);
+        return handledDrop;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
index 08edc2f..64f7be5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropOutlineDrawable.java
@@ -40,7 +40,7 @@
 import com.android.wm.shell.R;
 
 /**
- * Drawable to draw the region of the
+ * Drawable to draw the region that the target will have once it is dropped.
  */
 public class DropOutlineDrawable extends Drawable {
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index fc523aef..c4ce2cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -42,6 +42,7 @@
     private ComponentName mLastPipComponentName;
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
     private final DisplayLayout mDisplayLayout = new DisplayLayout();
+    private final @NonNull AnimatingBoundsState mAnimatingBoundsState = new AnimatingBoundsState();
 
     /**
      * Set the current PIP bounds.
@@ -151,6 +152,60 @@
         mPipReentryState = null;
     }
 
+    public AnimatingBoundsState getAnimatingBoundsState() {
+        return mAnimatingBoundsState;
+    }
+
+    /** Source of truth for the current animation bounds of PIP. */
+    public static class AnimatingBoundsState {
+        /** The bounds used when PIP is being dragged or animated. */
+        private final Rect mTemporaryBounds = new Rect();
+        /** The destination bounds to which PIP is animating. */
+        private final Rect mAnimatingToBounds = new Rect();
+
+        /** Whether PIP is being dragged or animated (e.g. resizing, in fling, etc). */
+        public boolean isAnimating() {
+            return !mTemporaryBounds.isEmpty();
+        }
+
+        /** Set the temporary bounds used to represent the drag or animation bounds of PIP. */
+        public void setTemporaryBounds(Rect bounds) {
+            mTemporaryBounds.set(bounds);
+        }
+
+        /** Set the bounds to which PIP is animating. */
+        public void setAnimatingToBounds(Rect bounds) {
+            mAnimatingToBounds.set(bounds);
+        }
+
+        /** Called when all ongoing dragging and animation operations have ended. */
+        public void onAllAnimationsEnded() {
+            mTemporaryBounds.setEmpty();
+        }
+
+        /** Called when an ongoing physics animation has ended. */
+        public void onPhysicsAnimationEnded() {
+            mAnimatingToBounds.setEmpty();
+        }
+
+        /** Returns the temporary animation bounds. */
+        public Rect getTemporaryBounds() {
+            return mTemporaryBounds;
+        }
+
+        /** Returns the destination bounds to which PIP is currently animating. */
+        public Rect getAnimatingToBounds() {
+            return mAnimatingToBounds;
+        }
+
+        void dump(PrintWriter pw, String prefix) {
+            final String innerPrefix = prefix + "  ";
+            pw.println(prefix + AnimatingBoundsState.class.getSimpleName());
+            pw.println(innerPrefix + "mTemporaryBounds=" + mTemporaryBounds);
+            pw.println(innerPrefix + "mAnimatingToBounds=" + mAnimatingToBounds);
+        }
+    }
+
     static final class PipReentryState {
         private static final String TAG = PipReentryState.class.getSimpleName();
 
@@ -190,10 +245,12 @@
         pw.println(innerPrefix + "mAspectRatio=" + mAspectRatio);
         pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
         pw.println(innerPrefix + "mDisplayLayout=" + mDisplayLayout);
+        pw.println(innerPrefix + "mIsStashed=" + mIsStashed);
         if (mPipReentryState == null) {
             pw.println(innerPrefix + "mPipReentryState=null");
         } else {
             mPipReentryState.dump(pw, innerPrefix);
         }
+        mAnimatingBoundsState.dump(pw, innerPrefix);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 833924c..39772fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -135,6 +135,7 @@
     private final Handler mUpdateHandler;
     private final PipBoundsState mPipBoundsState;
     private final PipBoundsHandler mPipBoundsHandler;
+    // TODO(b/172286265): Remove dependency on .pip.PHONE.PipMenuActivityController
     private final PipMenuActivityController mMenuActivityController;
     private final PipAnimationController mPipAnimationController;
     private final PipUiEventLogger mPipUiEventLoggerLogger;
@@ -946,10 +947,9 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, destinationBounds)
                 .round(tx, mLeash, mState.isInPip());
-        if (mMenuActivityController.isMenuVisible()) {
-            runOnMainHandler(() -> {
-                mMenuActivityController.resizePipMenu(mLeash, tx, destinationBounds);
-            });
+        if (mMenuActivityController != null && mMenuActivityController.isMenuVisible()) {
+            runOnMainHandler(() ->
+                    mMenuActivityController.resizePipMenu(mLeash, tx, destinationBounds));
         } else {
             tx.apply();
         }
@@ -973,10 +973,9 @@
 
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds);
-        if (mMenuActivityController.isMenuVisible()) {
-            runOnMainHandler(() -> {
-                mMenuActivityController.movePipMenu(mLeash, tx, destinationBounds);
-            });
+        if (mMenuActivityController != null && mMenuActivityController.isMenuVisible()) {
+            runOnMainHandler(() ->
+                    mMenuActivityController.movePipMenu(mLeash, tx, destinationBounds));
         } else {
             tx.apply();
         }
@@ -993,7 +992,8 @@
         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
             removePipImmediately();
             return;
-        } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
+        } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA
+                && mMenuActivityController != null) {
             // TODO: Synchronize this correctly in #applyEnterPipSyncTransaction
             finishResizeForMenu(destinationBounds);
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 9247c68..83cd63c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -42,7 +42,6 @@
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
 
-import java.io.PrintWriter;
 import java.util.function.Consumer;
 
 import kotlin.Unit;
@@ -80,20 +79,6 @@
     /** The region that all of PIP must stay within. */
     private final Rect mFloatingAllowedArea = new Rect();
 
-    /**
-     * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP
-     * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into
-     * and expanding out of the magnetic dismiss target.
-     *
-     * Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary
-     * bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to
-     * its new bounds.
-     */
-    private final Rect mTemporaryBounds = new Rect();
-
-    /** The destination bounds to which PIP is animating. */
-    private final Rect mAnimatingToBounds = new Rect();
-
     private int mStashOffset = 0;
 
     /** Coordinator instance for resolving conflicts with other floating content. */
@@ -108,15 +93,15 @@
             });
 
     /**
-     * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations.
+     * PhysicsAnimator instance for animating {@link PipBoundsState#getAnimatingBoundsState()}
+     * using physics animations.
      */
-    private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
-            mTemporaryBounds);
+    private final PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator;
 
     private MagnetizedObject<Rect> mMagnetizedPip;
 
     /**
-     * Update listener that resizes the PIP to {@link #mTemporaryBounds}.
+     * Update listener that resizes the PIP to {@link PipBoundsState#getAnimatingBoundsState()}.
      */
     private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener;
 
@@ -189,14 +174,17 @@
         mSnapAlgorithm = snapAlgorithm;
         mFloatingContentCoordinator = floatingContentCoordinator;
         mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
+        mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
+                mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
         mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
                 mSfAnimationHandlerThreadLocal.get());
+
         reloadResources();
 
         mResizePipUpdateListener = (target, values) -> {
-            if (!mTemporaryBounds.isEmpty()) {
-                mPipTaskOrganizer.scheduleUserResizePip(
-                        getBounds(), mTemporaryBounds, null);
+            if (mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
+                mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
+                        mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(), null);
             }
         };
     }
@@ -209,7 +197,8 @@
     @NonNull
     @Override
     public Rect getFloatingBoundsOnScreen() {
-        return !mAnimatingToBounds.isEmpty() ? mAnimatingToBounds : getBounds();
+        return !mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds().isEmpty()
+                ? mPipBoundsState.getAnimatingBoundsState().getAnimatingToBounds() : getBounds();
     }
 
     @NonNull
@@ -227,18 +216,14 @@
      * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations.
      */
     void synchronizePinnedStackBounds() {
-        cancelAnimations();
-        mTemporaryBounds.setEmpty();
+        cancelPhysicsAnimation();
+        mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
 
         if (mPipTaskOrganizer.isInPip()) {
             mFloatingContentCoordinator.onContentMoved(this);
         }
     }
 
-    boolean isAnimating() {
-        return mTemporaryBoundsPhysicsAnimator.isRunning();
-    }
-
     /**
      * Tries to move the pinned stack to the given {@param bounds}.
      */
@@ -261,14 +246,14 @@
         if (!mSpringingToTouch) {
             // If we are moving PIP directly to the touch event locations, cancel any animations and
             // move PIP to the given bounds.
-            cancelAnimations();
+            cancelPhysicsAnimation();
 
             if (!isDragging) {
                 resizePipUnchecked(toBounds);
                 mPipBoundsState.setBounds(toBounds);
             } else {
-                mTemporaryBounds.set(toBounds);
-                mPipTaskOrganizer.scheduleUserResizePip(getBounds(), mTemporaryBounds,
+                mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(toBounds);
+                mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
                         (Rect newBounds) -> {
                             mMainHandler.post(() -> {
                                 mMenuController.updateMenuLayout(newBounds);
@@ -303,9 +288,9 @@
         final float destinationY = targetCenter.y - (desiredHeight / 2f);
 
         // If we're already in the dismiss target area, then there won't be a move to set the
-        // temporary bounds, so just initialize it to the current bounds
-        if (mTemporaryBounds.isEmpty()) {
-            mTemporaryBounds.set(getBounds());
+        // temporary bounds, so just initialize it to the current bounds.
+        if (!mPipBoundsState.getAnimatingBoundsState().isAnimating()) {
+            mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
         }
         mTemporaryBoundsPhysicsAnimator
                 .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig)
@@ -339,7 +324,7 @@
             Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
-        cancelAnimations();
+        cancelPhysicsAnimation();
         mMenuController.hideMenuWithoutResize();
         mPipTaskOrganizer.getUpdateHandler().post(() -> {
             mPipTaskOrganizer.exitPip(skipAnimation
@@ -356,7 +341,7 @@
         if (DEBUG) {
             Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, "    "));
         }
-        cancelAnimations();
+        cancelPhysicsAnimation();
         mMenuController.hideMenuWithoutResize();
         mPipTaskOrganizer.removePip();
     }
@@ -383,14 +368,6 @@
     }
 
     /**
-     * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
-     * otherwise.
-     */
-    Rect getPossiblyAnimatingBounds() {
-        return mTemporaryBounds.isEmpty() ? getBounds() : mTemporaryBounds;
-    }
-
-    /**
      * Flings the PiP to the closest snap target.
      */
     void flingToSnapTarget(
@@ -428,9 +405,10 @@
                 : mMovementBounds.right;
 
         final float xEndValue = velocityX < 0 ? leftEdge : rightEdge;
+
+        final int startValueY = mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds().top;
         final float estimatedFlingYEndValue =
-                PhysicsAnimator.estimateFlingEndValue(
-                        mTemporaryBounds.top, velocityY, mFlingConfigY);
+                PhysicsAnimator.estimateFlingEndValue(startValueY, velocityY, mFlingConfigY);
 
         startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */,
                 false /* dismiss */);
@@ -443,7 +421,7 @@
     void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) {
         if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
             // Animate from the current bounds if we're not already animating.
-            mTemporaryBounds.set(getBounds());
+            mPipBoundsState.getAnimatingBoundsState().setTemporaryBounds(getBounds());
         }
 
         mTemporaryBoundsPhysicsAnimator
@@ -513,7 +491,7 @@
             Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset
                     + " callers=\n" + Debug.getCallers(5, "    "));
         }
-        cancelAnimations();
+        cancelPhysicsAnimation();
         mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
                 mUpdateBoundsCallback);
     }
@@ -521,9 +499,9 @@
     /**
      * Cancels all existing animations.
      */
-    private void cancelAnimations() {
+    private void cancelPhysicsAnimation() {
         mTemporaryBoundsPhysicsAnimator.cancel();
-        mAnimatingToBounds.setEmpty();
+        mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
         mSpringingToTouch = false;
     }
 
@@ -547,22 +525,19 @@
      */
     private void startBoundsAnimator(float toX, float toY, boolean dismiss) {
         if (!mSpringingToTouch) {
-            cancelAnimations();
+            cancelPhysicsAnimation();
         }
 
-        // Set animatingToBounds directly to avoid allocating a new Rect, but then call
-        // setAnimatingToBounds to run the normal logic for changing animatingToBounds.
-        mAnimatingToBounds.set(
+        setAnimatingToBounds(new Rect(
                 (int) toX,
                 (int) toY,
                 (int) toX + getBounds().width(),
-                (int) toY + getBounds().height());
-        setAnimatingToBounds(mAnimatingToBounds);
+                (int) toY + getBounds().height()));
 
         if (!mTemporaryBoundsPhysicsAnimator.isRunning()) {
             mTemporaryBoundsPhysicsAnimator
                     .addUpdateListener(mResizePipUpdateListener)
-                    .withEndActions(this::onBoundsAnimationEnd);
+                    .withEndActions(this::onBoundsPhysicsAnimationEnd);
         }
 
         mTemporaryBoundsPhysicsAnimator.start();
@@ -576,31 +551,34 @@
         mDismissalPending = true;
     }
 
-    private void onBoundsAnimationEnd() {
+    private void onBoundsPhysicsAnimationEnd() {
+        // The physics animation ended, though we may not necessarily be done animating, such as
+        // when we're still dragging after moving out of the magnetic target.
         if (!mDismissalPending
                 && !mSpringingToTouch
                 && !mMagnetizedPip.getObjectStuckToTarget()) {
-            mPipBoundsState.setBounds(mTemporaryBounds);
+            // All animations (including dragging) have actually finished.
+            mPipBoundsState.setBounds(
+                    mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds());
+            mPipBoundsState.getAnimatingBoundsState().onAllAnimationsEnded();
             if (!mDismissalPending) {
                 // do not schedule resize if PiP is dismissing, which may cause app re-open to
                 // mBounds instead of it's normal bounds.
                 mPipTaskOrganizer.scheduleFinishResizePip(getBounds());
             }
-            mTemporaryBounds.setEmpty();
         }
-
-        mAnimatingToBounds.setEmpty();
+        mPipBoundsState.getAnimatingBoundsState().onPhysicsAnimationEnded();
         mSpringingToTouch = false;
         mDismissalPending = false;
     }
 
     /**
-     * Notifies the floating coordinator that we're moving, and sets {@link #mAnimatingToBounds} so
+     * Notifies the floating coordinator that we're moving, and sets the animating to bounds so
      * we return these bounds from
      * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
      */
     private void setAnimatingToBounds(Rect bounds) {
-        mAnimatingToBounds.set(bounds);
+        mPipBoundsState.getAnimatingBoundsState().setAnimatingToBounds(bounds);
         mFloatingContentCoordinator.onContentMoved(this);
     }
 
@@ -639,7 +617,8 @@
     MagnetizedObject<Rect> getMagnetizedPip() {
         if (mMagnetizedPip == null) {
             mMagnetizedPip = new MagnetizedObject<Rect>(
-                    mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+                    mContext, mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds(),
+                    FloatProperties.RECT_X, FloatProperties.RECT_Y) {
                 @Override
                 public float getWidth(@NonNull Rect animatedPipBounds) {
                     return animatedPipBounds.width();
@@ -661,10 +640,4 @@
 
         return mMagnetizedPip;
     }
-
-    public void dump(PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        pw.println(prefix + TAG);
-        pw.println(innerPrefix + "mBounds=" + getBounds());
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index d820e77..c04e7e8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -710,7 +710,7 @@
                 return;
             }
 
-            Rect bounds = mMotionHelper.getPossiblyAnimatingBounds();
+            Rect bounds = getPossiblyAnimatingBounds();
             mDelta.set(0f, 0f);
             mStartPosition.set(bounds.left, bounds.top);
             mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
@@ -747,7 +747,7 @@
                 mDelta.x += left - lastX;
                 mDelta.y += top - lastY;
 
-                mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds());
+                mTmpBounds.set(getPossiblyAnimatingBounds());
                 mTmpBounds.offsetTo((int) left, (int) top);
                 mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
 
@@ -783,7 +783,7 @@
 
                 // Reset the touch state on up before the fling settles
                 mTouchState.reset();
-                final Rect animatingBounds = mMotionHelper.getPossiblyAnimatingBounds();
+                final Rect animatingBounds = getPossiblyAnimatingBounds();
                 // If User releases the PIP window while it's out of the display bounds, put
                 // PIP into stashed mode.
                 if (mEnableStash
@@ -872,6 +872,16 @@
                 || mExpandedBounds.height() != mNormalBounds.height();
     }
 
+    /**
+     * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds
+     * otherwise.
+     */
+    Rect getPossiblyAnimatingBounds() {
+        return mPipBoundsState.getAnimatingBoundsState().isAnimating()
+                ? mPipBoundsState.getAnimatingBoundsState().getTemporaryBounds()
+                : mPipBoundsState.getBounds();
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
@@ -889,7 +899,6 @@
         pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
         mPipBoundsHandler.dump(pw, innerPrefix);
         mTouchState.dump(pw, innerPrefix);
-        mMotionHelper.dump(pw, innerPrefix);
         if (mPipResizeGestureHandler != null) {
             mPipResizeGestureHandler.dump(pw, innerPrefix);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 985dff2..d117673 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -83,4 +83,9 @@
      * @return {@code true} if it successes to split the primary task.
      */
     boolean splitPrimaryTask();
+
+    /**
+     * Exits the split to make the primary task fullscreen.
+     */
+    void dismissSplitToPrimaryTask();
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 8b616e8..341a459 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -501,6 +501,11 @@
         }
     }
 
+    @Override
+    public void dismissSplitToPrimaryTask() {
+        startDismissSplit(true /* toPrimaryTask */);
+    }
+
     /** Notifies the bounds of split screen changed. */
     void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
         synchronized (mBoundsChangedListeners) {
@@ -519,8 +524,8 @@
         mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
     }
 
-    void startDismissSplit() {
-        mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, true /* dismissOrMaximize */);
+    void startDismissSplit(boolean toPrimaryTask) {
+        mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
         updateVisibility(false /* visible */);
         mMinimized = false;
         // Resets divider bar position to undefined, so new divider bar will apply default position
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
index f709fed..64e9d66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java
@@ -271,7 +271,7 @@
                     Log.d(TAG, "    was in split, so this means leave it "
                             + mPrimary.topActivityType + "  " + mSecondary.topActivityType);
                 }
-                mSplitScreenController.startDismissSplit();
+                mSplitScreenController.startDismissSplit(false /* toPrimaryTask */);
             } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
                 // Wasn't in split-mode (both were empty), but now that the primary split is
                 // populated, we should fully enter split by moving everything else into secondary.
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
index 8b2f668..58fc90e 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidManifest.xml
@@ -32,8 +32,19 @@
     <!-- Workaround grant runtime permission exception from b/152733071 -->
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
     <uses-permission android:name="android.permission.READ_LOGS"/>
+    <!-- Force-stop test apps -->
+    <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
     <application>
         <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".NotificationListener"
+                 android:exported="true"
+                 android:label="WMShellTestsNotificationListenerService"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService" />
+            </intent-filter>
+        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
new file mode 100644
index 0000000..ef0390f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.content.ComponentName
+
+const val IME_WINDOW_NAME = "InputMethod"
+const val PIP_WINDOW_NAME = "PipMenuActivity"
+
+// Test App
+const val TEST_APP_PACKAGE_NAME = "com.android.wm.shell.flicker.testapp"
+// Test App > Pip Activity
+val TEST_APP_PIP_ACTIVITY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
+        TEST_APP_PACKAGE_NAME, ".PipActivity")
+const val TEST_APP_PIP_ACTIVITY_LABEL = "PipApp"
+const val TEST_APP_PIP_ACTIVITY_WINDOW_NAME = "PipActivity"
+// Test App > Ime Activity
+val TEST_APP_IME_ACTIVITY_COMPONENT_NAME: ComponentName = ComponentName.createRelative(
+        TEST_APP_PACKAGE_NAME, ".ImeActivity")
+const val TEST_APP_IME_ACTIVITY_LABEL = "ImeApp"
+
+const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 99f824b..75b55c1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.flicker
 
+import android.content.pm.PackageManager
 import android.os.RemoteException
 import android.os.SystemClock
 import android.platform.helpers.IAppHelper
@@ -41,6 +42,9 @@
     val uiDevice by lazy {
         UiDevice.getInstance(instrumentation)
     }
+    val packageManager: PackageManager by lazy {
+        instrumentation.context.getPackageManager()
+    }
 
     /**
      * Build a test tag for the test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
new file mode 100644
index 0000000..5c7ec30
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/NotificationListener.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.service.notification.NotificationListenerService
+import android.service.notification.StatusBarNotification
+import android.util.Log
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+
+class NotificationListener : NotificationListenerService() {
+    private val notifications: MutableMap<Any, StatusBarNotification> = mutableMapOf()
+
+    override fun onNotificationPosted(sbn: StatusBarNotification) {
+        if (DEBUG) Log.d(TAG, "onNotificationPosted: $sbn")
+        notifications[sbn.key] = sbn
+    }
+
+    override fun onNotificationRemoved(sbn: StatusBarNotification) {
+        if (DEBUG) Log.d(TAG, "onNotificationRemoved: $sbn")
+        notifications.remove(sbn.key)
+    }
+
+    override fun onListenerConnected() {
+        if (DEBUG) Log.d(TAG, "onListenerConnected")
+        instance = this
+    }
+
+    override fun onListenerDisconnected() {
+        if (DEBUG) Log.d(TAG, "onListenerDisconnected")
+        instance = null
+        notifications.clear()
+    }
+
+    companion object {
+        private const val DEBUG = false
+        private const val TAG = "WMShellFlickerTests_NotificationListener"
+
+        private const val CMD_NOTIFICATION_ALLOW_LISTENER = "cmd notification allow_listener %s"
+        private const val CMD_NOTIFICATION_DISALLOW_LISTENER =
+                "cmd notification disallow_listener %s"
+        private const val COMPONENT_NAME = "com.android.wm.shell.flicker/.NotificationListener"
+
+        private var instance: NotificationListener? = null
+
+        fun startNotificationListener(): Boolean {
+            if (instance != null) {
+                return true
+            }
+
+            runShellCommand(CMD_NOTIFICATION_ALLOW_LISTENER.format(COMPONENT_NAME))
+            return wait { instance != null }
+        }
+
+        fun stopNotificationListener(): Boolean {
+            if (instance == null) {
+                return true
+            }
+
+            runShellCommand(CMD_NOTIFICATION_DISALLOW_LISTENER.format(COMPONENT_NAME))
+            return wait { instance == null }
+        }
+
+        fun waitForNotificationToAppear(predicate: (StatusBarNotification) -> Boolean): Boolean {
+            return instance?.let {
+                wait { it.notifications.values.any(predicate) }
+            } ?: throw IllegalStateException("NotificationListenerService is not connected")
+        }
+
+        fun waitForNotificationToDisappear(predicate: (StatusBarNotification) -> Boolean): Boolean {
+            return instance?.let {
+                wait { it.notifications.values.none(predicate) }
+            } ?: throw IllegalStateException("NotificationListenerService is not connected")
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
new file mode 100644
index 0000000..a6d6735
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/WaitUtils.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.wm.shell.flicker
+
+import android.os.SystemClock
+
+private const val DEFAULT_TIMEOUT = 10000L
+private const val DEFAULT_POLL_INTERVAL = 1000L
+
+fun wait(condition: () -> Boolean): Boolean {
+    val (success, _) = waitForResult(extractor = condition, validator = { it })
+    return success
+}
+
+fun <R> waitForResult(
+    timeout: Long = DEFAULT_TIMEOUT,
+    interval: Long = DEFAULT_POLL_INTERVAL,
+    extractor: () -> R,
+    validator: (R) -> Boolean = { it != null }
+): Pair<Boolean, R?> {
+    val startTime = SystemClock.uptimeMillis()
+    do {
+        val result = extractor()
+        if (validator(result)) {
+            return (true to result)
+        }
+        SystemClock.sleep(interval)
+    } while (SystemClock.uptimeMillis() - startTime < timeout)
+
+    return (false to null)
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
new file mode 100644
index 0000000..e8cf7d9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/BaseAppHelper.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.wm.shell.flicker.helpers
+
+import android.app.ActivityManager
+import android.app.Instrumentation
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager.FEATURE_LEANBACK
+import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
+import android.support.test.launcherhelper.LauncherStrategyFactory
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.wm.shell.flicker.TEST_APP_PACKAGE_NAME
+
+abstract class BaseAppHelper(
+    instrumentation: Instrumentation,
+    launcherName: String,
+    private val launcherActivityComponent: ComponentName
+) : StandardAppHelper(
+        instrumentation,
+        TEST_APP_PACKAGE_NAME,
+        launcherName,
+        LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy
+) {
+    protected val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
+
+    private val context: Context
+        get() = mInstrumentation.context
+
+    private val activityManager: ActivityManager?
+        get() = context.getSystemService(ActivityManager::class.java)
+
+    private val appSelector = By.pkg(packageName).depth(0)
+
+    protected val isTelevision: Boolean
+        get() = context.packageManager.run {
+            hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
+        }
+
+    val label: String
+        get() = context.packageManager.run {
+            getApplicationLabel(getApplicationInfo(packageName, 0)).toString()
+        }
+
+    fun launchViaIntent() {
+        context.startActivity(openAppIntent)
+
+        uiDevice.wait(Until.hasObject(appSelector), APP_LAUNCH_WAIT_TIME_MS)
+    }
+
+    fun forceStop() = activityManager?.forceStopPackage(packageName)
+
+    override fun getOpenAppIntent(): Intent {
+        val intent = Intent()
+        intent.component = launcherActivityComponent
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        return intent
+    }
+
+    companion object {
+        private const val APP_LAUNCH_WAIT_TIME_MS = 10_000L
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt
deleted file mode 100644
index 47a62ce..0000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/FlickerAppHelper.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.helpers
-
-import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-
-abstract class FlickerAppHelper(
-    instr: Instrumentation,
-    launcherName: String,
-    launcherStrategy: ILauncherStrategy
-) : StandardAppHelper(instr, sFlickerPackage, launcherName, launcherStrategy) {
-    companion object {
-        var sFlickerPackage = "com.android.wm.shell.flicker.testapp"
-    }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
index 0cedc0a..a6650d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt
@@ -17,37 +17,36 @@
 package com.android.wm.shell.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.FIND_TIMEOUT
 import com.android.server.wm.flicker.helpers.waitForIME
+import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_COMPONENT_NAME
+import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_LABEL
 import org.junit.Assert
 
 open class ImeAppHelper(
-    instr: Instrumentation,
-    launcherName: String = "ImeApp",
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
-) : FlickerAppHelper(instr, launcherName, launcherStrategy) {
-    open fun openIME(device: UiDevice) {
-        val editText = device.wait(
+    instrumentation: Instrumentation
+) : BaseAppHelper(
+        instrumentation,
+        TEST_APP_IME_ACTIVITY_LABEL,
+        TEST_APP_IME_ACTIVITY_COMPONENT_NAME
+) {
+    fun openIME() {
+        val editText = uiDevice.wait(
                 Until.findObject(By.res(getPackage(), "plain_text_input")),
                 FIND_TIMEOUT)
         Assert.assertNotNull("Text field not found, this usually happens when the device " +
                 "was left in an unknown state (e.g. in split screen)", editText)
         editText.click()
-        if (!device.waitForIME()) {
+        if (!uiDevice.waitForIME()) {
             Assert.fail("IME did not appear")
         }
     }
 
-    open fun closeIME(device: UiDevice) {
-        device.pressBack()
+    fun closeIME() {
+        uiDevice.pressBack()
         // Using only the AccessibilityInfo it is not possible to identify if the IME is active
-        device.waitForIdle(1000)
+        uiDevice.waitForIdle(1000)
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index 5391702..d5efa40 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -17,29 +17,56 @@
 package com.android.wm.shell.flicker.helpers
 
 import android.app.Instrumentation
-import android.support.test.launcherhelper.ILauncherStrategy
-import android.support.test.launcherhelper.LauncherStrategyFactory
+import android.os.SystemClock
+import android.view.KeyEvent.KEYCODE_WINDOW
 import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
-import com.android.server.wm.flicker.helpers.hasPipWindow
+import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.helpers.closePipWindow
-import org.junit.Assert
+import com.android.server.wm.flicker.helpers.hasPipWindow
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_COMPONENT_NAME
+import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_LABEL
+import org.junit.Assert.assertNotNull
 
 class PipAppHelper(
-    instr: Instrumentation,
-    launcherStrategy: ILauncherStrategy = LauncherStrategyFactory
-            .getInstance(instr)
-            .launcherStrategy
-) : FlickerAppHelper(instr, "PipApp", launcherStrategy) {
-    fun clickEnterPipButton(device: UiDevice) {
-        val enterPipButton = device.findObject(By.res(getPackage(), "enter_pip"))
-        Assert.assertNotNull("Pip button not found, this usually happens when the device " +
+    instrumentation: Instrumentation
+) : BaseAppHelper(
+        instrumentation,
+        TEST_APP_PIP_ACTIVITY_LABEL,
+        TEST_APP_PIP_ACTIVITY_COMPONENT_NAME
+) {
+    fun clickEnterPipButton() {
+        val enterPipButton = uiDevice.findObject(By.res(packageName, "enter_pip"))
+        assertNotNull("Pip button not found, this usually happens when the device " +
                 "was left in an unknown state (e.g. in split screen)", enterPipButton)
         enterPipButton.click()
-        device.hasPipWindow()
+
+        // TODO(b/172321238): remove this check once hasPipWindow is fixed on TVs
+        if (!isTelevision) {
+            uiDevice.hasPipWindow()
+        } else {
+            // Simply wait for 3 seconds
+            SystemClock.sleep(3_000)
+        }
     }
 
-    fun closePipWindow(device: UiDevice) {
-        device.closePipWindow()
+    fun closePipWindow() {
+        // TODO(b/172321238): remove this check once and simply call closePipWindow once the TV
+        //  logic is integrated there.
+        if (!isTelevision) {
+            uiDevice.closePipWindow()
+        } else {
+            // Bring up Pip menu
+            uiDevice.pressKeyCode(KEYCODE_WINDOW)
+
+            // Wait for the menu to come up and render the close button
+            val closeButton = uiDevice.wait(
+                    Until.findObject(By.res(SYSTEM_UI_PACKAGE_NAME, "close_button")), 3_000)
+            assertNotNull("Pip menu close button is not found", closeButton)
+            closeButton.click()
+
+            // Give it 1 second, just in case
+            SystemClock.sleep(1_000)
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 010aa0d..a1da7c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -31,6 +31,7 @@
 import com.android.wm.shell.flicker.statusBarLayerIsAlwaysVisible
 import com.android.wm.shell.flicker.statusBarLayerRotatesScales
 import com.android.wm.shell.flicker.statusBarWindowIsAlwaysVisible
+import com.android.wm.shell.flicker.PIP_WINDOW_NAME
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -79,7 +80,7 @@
                 }
             }
             transitions {
-                testApp.clickEnterPipButton(device)
+                testApp.clickEnterPipButton()
                 device.expandPipWindow()
             }
             assertions {
@@ -89,7 +90,7 @@
                     all("pipWindowBecomesVisible") {
                         this.showsAppWindow(testApp.`package`)
                                 .then()
-                                .showsAppWindow(sPipWindowTitle)
+                                .showsAppWindow(PIP_WINDOW_NAME)
                     }
                 }
 
@@ -103,7 +104,7 @@
                     all("pipLayerBecomesVisible") {
                         this.showsLayer(testApp.launcherName)
                                 .then()
-                                .showsLayer(sPipWindowTitle)
+                                .showsLayer(PIP_WINDOW_NAME)
                     }
                 }
             }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
index 43e0225..d343f2a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt
@@ -18,7 +18,6 @@
 
 import android.content.ComponentName
 import android.graphics.Region
-import android.support.test.launcherhelper.LauncherStrategyFactory
 import android.util.Log
 import android.view.Surface
 import android.view.WindowManager
@@ -29,6 +28,9 @@
 import com.android.server.wm.flicker.helpers.closePipWindow
 import com.android.server.wm.flicker.helpers.hasPipWindow
 import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.TEST_APP_IME_ACTIVITY_COMPONENT_NAME
+import com.android.wm.shell.flicker.IME_WINDOW_NAME
+import com.android.wm.shell.flicker.TEST_APP_PIP_ACTIVITY_WINDOW_NAME
 import com.android.wm.shell.flicker.helpers.ImeAppHelper
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -51,19 +53,11 @@
     private val windowManager: WindowManager =
             instrumentation.context.getSystemService(WindowManager::class.java)
 
-    private val keyboardApp = ImeAppHelper(instrumentation, "ImeApp",
-            LauncherStrategyFactory.getInstance(instrumentation).launcherStrategy)
-
-    private val KEYBOARD_ACTIVITY: ComponentName = ComponentName.createRelative(
-            "com.android.wm.shell.flicker.testapp", ".ImeActivity")
-    private val PIP_ACTIVITY_WINDOW_NAME = "PipActivity"
-    private val INPUT_METHOD_WINDOW_NAME = "InputMethod"
-
-    private val testRepetitions = 10
+    private val keyboardApp = ImeAppHelper(instrumentation)
 
     private val keyboardScenario: FlickerBuilder
         get() = FlickerBuilder(instrumentation).apply {
-            repeat { testRepetitions }
+            repeat { TEST_REPETITIONS }
             // disable layer tracing
             withLayerTracing { null }
             setup {
@@ -73,11 +67,11 @@
                     // launch our target pip app
                     testApp.open()
                     this.setRotation(rotation)
-                    testApp.clickEnterPipButton(device)
+                    testApp.clickEnterPipButton()
                     // open an app with an input field and a keyboard
                     // UiAutomator doesn't support to launch the multiple Activities in a task.
                     // So use launchActivity() for the Keyboard Activity.
-                    launchActivity(KEYBOARD_ACTIVITY)
+                    launchActivity(TEST_APP_IME_ACTIVITY_COMPONENT_NAME)
                 }
             }
             teardown {
@@ -101,10 +95,10 @@
             withTestName { testTag }
             transitions {
                 // open the soft keyboard
-                keyboardApp.openIME(device)
+                keyboardApp.openIME()
 
                 // then close it again
-                keyboardApp.closeIME(device)
+                keyboardApp.closeIME()
             }
             assertions {
                 windowManagerTrace {
@@ -127,18 +121,18 @@
             withTestName { testTag }
             transitions {
                 // open the soft keyboard
-                keyboardApp.openIME(device)
+                keyboardApp.openIME()
             }
             teardown {
                 eachRun {
                     // close the keyboard
-                    keyboardApp.closeIME(device)
+                    keyboardApp.closeIME()
                 }
             }
             assertions {
                 windowManagerTrace {
                     end {
-                        isAboveWindow(INPUT_METHOD_WINDOW_NAME, PIP_ACTIVITY_WINDOW_NAME)
+                        isAboveWindow(IME_WINDOW_NAME, TEST_APP_PIP_ACTIVITY_WINDOW_NAME)
                     }
                 }
             }
@@ -207,6 +201,8 @@
     }
 
     companion object {
+        private const val TEST_REPETITIONS = 10
+
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<Array<Any>> {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
index 3822d69..c1c34ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTestBase.kt
@@ -24,8 +24,4 @@
     rotation: Int
 ) : NonRotationTestBase(rotationName, rotation) {
     protected val testApp = PipAppHelper(instrumentation)
-
-    companion object {
-        const val sPipWindowTitle = "PipMenuActivity"
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
new file mode 100644
index 0000000..569da1d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipNotificationTests.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.wm.shell.flicker.pip.tv
+
+import android.app.Notification
+import android.service.notification.StatusBarNotification
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.NotificationListener.Companion.startNotificationListener
+import com.android.wm.shell.flicker.NotificationListener.Companion.stopNotificationListener
+import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToAppear
+import com.android.wm.shell.flicker.NotificationListener.Companion.waitForNotificationToDisappear
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test Pip Notifications on TV.
+ * To run this test: `atest WMShellFlickerTests:TvPipNotificationTests`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class TvPipNotificationTests(rotationName: String, rotation: Int)
+    : TvPipTestBase(rotationName, rotation) {
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        val started = startNotificationListener()
+        if (!started) {
+            error("NotificationListener hasn't started")
+        }
+    }
+
+    @After
+    override fun tearDown() {
+        stopNotificationListener()
+        testApp.forceStop()
+        super.tearDown()
+    }
+
+    @Test
+    fun pipNotification_postedAndDismissed() {
+        testApp.launchViaIntent()
+        testApp.clickEnterPipButton()
+
+        assertTrue("Pip notification should have been posted",
+                waitForNotificationToAppear { it.isPipNotificationWithTitle(testApp.label) })
+
+        testApp.closePipWindow()
+
+        assertTrue("Pip notification should have been dismissed",
+                waitForNotificationToDisappear { it.isPipNotificationWithTitle(testApp.label) })
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): Collection<Array<Any>> {
+            val supportedRotations = intArrayOf(Surface.ROTATION_0)
+            return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+        }
+    }
+}
+
+private const val PIP_NOTIFICATION_TAG = "PipNotification"
+
+private val StatusBarNotification.title: String
+    get() = notification?.extras?.getString(Notification.EXTRA_TITLE) ?: ""
+
+private fun StatusBarNotification.isPipNotificationWithTitle(expectedTitle: String): Boolean =
+    tag == PIP_NOTIFICATION_TAG && title == expectedTitle
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
new file mode 100644
index 0000000..104248c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/tv/TvPipTestBase.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.wm.shell.flicker.pip.tv
+
+import android.content.pm.PackageManager.FEATURE_LEANBACK
+import android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.wm.shell.flicker.pip.PipTestBase
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+
+abstract class TvPipTestBase(rotationName: String, rotation: Int)
+    : PipTestBase(rotationName, rotation) {
+
+    private val isTelevision: Boolean
+        get() = packageManager.run {
+            hasSystemFeature(FEATURE_LEANBACK) || hasSystemFeature(FEATURE_LEANBACK_ONLY)
+        }
+
+    @Before
+    open fun setUp() {
+        Assume.assumeTrue(isTelevision)
+        uiDevice.wakeUpAndGoToHomeScreen()
+    }
+
+    @After
+    open fun tearDown() {
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
index a8f795e..59d9104 100644
--- a/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/unittest/AndroidManifest.xml
@@ -22,6 +22,13 @@
     <application android:debuggable="true" android:largeHeap="true">
         <uses-library android:name="android.test.mock" />
         <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".bubbles.BubblesTestActivity"
+            android:allowEmbedded="true"
+            android:documentLaunchMode="always"
+            android:excludeFromRecents="true"
+            android:exported="false"
+            android:resizeableActivity="true" />
     </application>
 
     <instrumentation
diff --git a/libs/WindowManager/Shell/tests/unittest/res/layout/main.xml b/libs/WindowManager/Shell/tests/unittest/res/layout/main.xml
new file mode 100644
index 0000000..0d09f86
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/res/layout/main.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="this is a test activity"
+    />
+    <EditText
+        android:layout_height="wrap_content"
+        android:id="@+id/editText1"
+        android:layout_width="match_parent">
+        <requestFocus></requestFocus>
+    </EditText>
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
similarity index 79%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index fdebe4e..5bdf831 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip;
+package com.android.wm.shell;
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
@@ -24,17 +24,18 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import org.junit.After;
 import org.junit.Before;
 
 /**
- * Base class that does One Handed specific setup.
+ * Base class that does shell test case setup.
  */
-public abstract class PipTestCase {
+public abstract class ShellTestCase {
 
     protected TestableContext mContext;
 
     @Before
-    public void setup() {
+    public void shellSetup() {
         final Context context =
                 InstrumentationRegistry.getInstrumentation().getTargetContext();
         final DisplayManager dm = context.getSystemService(DisplayManager.class);
@@ -47,6 +48,14 @@
                 .adoptShellPermissionIdentity();
     }
 
+    @After
+    public void shellTearDown() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
     protected Context getContext() {
         return mContext;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
index 5c14859..34f772f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TaskViewTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
@@ -30,7 +30,6 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -45,8 +44,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.SysuiTestCase;
-import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.HandlerExecutor;
 
 import org.junit.After;
@@ -60,8 +57,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
-public class TaskViewTest extends SysuiTestCase {
+public class TaskViewTest extends ShellTestCase {
 
     @Mock
     TaskView.Listener mViewListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 31c08ae..7adc411 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.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.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -38,8 +38,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleData.TimeSource;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.bubbles.BubbleData.TimeSource;
 
 import com.google.common.collect.ImmutableList;
 
@@ -63,7 +63,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class BubbleDataTest extends SysuiTestCase {
+public class BubbleDataTest extends ShellTestCase {
 
     private BubbleEntry mEntryA1;
     private BubbleEntry mEntryA2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.java
index fd6e2ee..5b77e4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleFlyoutViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleFlyoutViewTest.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.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotSame;
@@ -30,8 +30,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -42,7 +42,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class BubbleFlyoutViewTest extends SysuiTestCase {
+public class BubbleFlyoutViewTest extends ShellTestCase {
     private BubbleFlyoutView mFlyout;
     private TextView mFlyoutText;
     private TextView mSenderName;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index 690a1ad..0693052 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.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.bubbles;
+package com.android.wm.shell.bubbles;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -37,8 +37,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,7 +49,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class BubbleTest extends SysuiTestCase {
+public class BubbleTest extends ShellTestCase {
     @Mock
     private Notification mNotif;
     @Mock
@@ -72,7 +72,7 @@
         Intent target = new Intent(mContext, BubblesTestActivity.class);
         Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
                 PendingIntent.getActivity(mContext, 0, target, 0),
-                        Icon.createWithResource(mContext, R.drawable.android))
+                        Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
                 .build();
         when(mSbn.getNotification()).thenReturn(mNotif);
         when(mNotif.getBubbleMetadata()).thenReturn(metadata);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.java
similarity index 80%
copy from packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
copy to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.java
index 43d2ad1..d5fbe55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.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,21 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles;
+package com.android.wm.shell.bubbles;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 
-import com.android.systemui.R;
+import com.android.wm.shell.R;
 
 /**
  * Referenced by NotificationTestHelper#makeBubbleMetadata
  */
 public class BubblesTestActivity extends Activity {
 
-    public static final String BUBBLE_ACTIVITY_OPENED =
-            "com.android.systemui.bubbles.BUBBLE_ACTIVITY_OPENED";
+    public static final String BUBBLE_ACTIVITY_OPENED = "BUBBLE_ACTIVITY_OPENED";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.java
index a5bb8ea..9c4f341 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationControllerTest.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.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.mock;
@@ -34,8 +34,8 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
-import com.android.systemui.bubbles.BubblePositioner;
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.BubblePositioner;
 
 import org.junit.Before;
 import org.junit.Ignore;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTest.java
index 498330c..c4edbb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTest.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.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index a5f2e8b..a7a7db8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/PhysicsAnimationLayoutTestCase.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.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import static org.mockito.Mockito.when;
 
@@ -30,8 +30,8 @@
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
 
 import org.junit.Before;
 import org.mockito.Mock;
@@ -50,7 +50,7 @@
  *
  * See physics-animation-testing.md.
  */
-public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
+public class PhysicsAnimationLayoutTestCase extends ShellTestCase {
     TestablePhysicsAnimationLayout mLayout;
     List<View> mViews = new ArrayList<>();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.java
index 7d0abec..6b01462 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/StackAnimationControllerTest.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.bubbles.animation;
+package com.android.wm.shell.bubbles.animation;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
@@ -33,8 +33,8 @@
 import androidx.dynamicanimation.animation.SpringForce;
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
-import com.android.systemui.bubbles.BubblePositioner;
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
index 9b8fd11..4160280 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles.storage
+package com.android.wm.shell.bubbles.storage
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
+import com.android.wm.shell.ShellTestCase
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertNotNull
 import junit.framework.Assert.assertTrue
@@ -28,7 +28,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class BubblePersistentRepositoryTest : SysuiTestCase() {
+class BubblePersistentRepositoryTest : ShellTestCase() {
 
     private val bubbles = listOf(
             BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 7ea611c..4fab9a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles.storage
+package com.android.wm.shell.bubbles.storage
 
 import android.content.pm.LauncherApps
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.eq
+import com.android.wm.shell.ShellTestCase
 import junit.framework.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -32,7 +32,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class BubbleVolatileRepositoryTest : SysuiTestCase() {
+class BubbleVolatileRepositoryTest : ShellTestCase() {
 
     private val user0 = UserHandle.of(0)
     private val user10 = UserHandle.of(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
index 8cf4534..e0891a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.bubbles.storage
+package com.android.wm.shell.bubbles.storage
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
+import com.android.wm.shell.ShellTestCase
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertTrue
 import org.junit.Test
@@ -28,7 +28,7 @@
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
-class BubbleXmlHelperTest : SysuiTestCase() {
+class BubbleXmlHelperTest : ShellTestCase() {
 
     private val bubbles = listOf(
             BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
new file mode 100644
index 0000000..affd736
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -0,0 +1,358 @@
+/*
+ * 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.wm.shell.draganddrop;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
+import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
+import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
+
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.app.IActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.view.DisplayInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
+import com.android.wm.shell.splitscreen.DividerView;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+
+/**
+ * Tests for the drag and drop policy.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DragAndDropPolicyTest {
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private IActivityTaskManager mIActivityTaskManager;
+
+    @Mock
+    private SplitScreen mSplitScreen;
+
+    @Mock
+    private DragAndDropPolicy.Starter mStarter;
+
+    private DisplayLayout mDisplayLayout;
+    private Insets mInsets;
+    private DragAndDropPolicy mPolicy;
+
+    private ClipData mActivityClipData;
+    private ClipData mNonResizeableActivityClipData;
+    private ClipData mTaskClipData;
+    private ClipData mShortcutClipData;
+
+    private ActivityManager.RunningTaskInfo mHomeTask;
+    private ActivityManager.RunningTaskInfo mFullscreenAppTask;
+    private ActivityManager.RunningTaskInfo mNonResizeableFullscreenAppTask;
+    private ActivityManager.RunningTaskInfo mSplitPrimaryAppTask;
+
+    @Before
+    public void setUp() throws RemoteException {
+        MockitoAnnotations.initMocks(this);
+
+        Resources res = mock(Resources.class);
+        Configuration config = new Configuration();
+        doReturn(config).when(res).getConfiguration();
+        DisplayInfo info = new DisplayInfo();
+        info.logicalWidth = 100;
+        info.logicalHeight = 100;
+        mDisplayLayout = new DisplayLayout(info, res, false, false);
+        mInsets = Insets.of(0, 0, 0, 0);
+
+        DividerView divider = mock(DividerView.class);
+        doReturn(divider).when(mSplitScreen).getDividerView();
+        doReturn(new Rect(50, 0, 100, 100)).when(divider)
+                .getNonMinimizedSplitScreenSecondaryBounds();
+
+        mPolicy = new DragAndDropPolicy(mContext, mIActivityTaskManager, mSplitScreen, mStarter);
+        mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
+        mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY);
+        setClipDataResizeable(mNonResizeableActivityClipData, false);
+        mTaskClipData = createClipData(MIMETYPE_APPLICATION_TASK);
+        mShortcutClipData = createClipData(MIMETYPE_APPLICATION_SHORTCUT);
+
+        mHomeTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME);
+        mFullscreenAppTask = createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        mNonResizeableFullscreenAppTask =
+                createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        mNonResizeableFullscreenAppTask.isResizeable = false;
+        mSplitPrimaryAppTask = createTaskInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
+                ACTIVITY_TYPE_STANDARD);
+
+        setIsPhone(false);
+        setInSplitScreen(false);
+        setRunningTask(mFullscreenAppTask);
+    }
+
+    /**
+     * Creates a clip data that is by default resizeable.
+     */
+    private ClipData createClipData(String mimeType) {
+        ClipDescription clipDescription = new ClipDescription(mimeType, new String[] { mimeType });
+        Intent i = new Intent();
+        switch (mimeType) {
+            case MIMETYPE_APPLICATION_SHORTCUT:
+                i.putExtra(Intent.EXTRA_PACKAGE_NAME, "package");
+                i.putExtra(Intent.EXTRA_SHORTCUT_ID, "shortcut_id");
+                break;
+            case MIMETYPE_APPLICATION_TASK:
+                i.putExtra(Intent.EXTRA_TASK_ID, 12345);
+                break;
+            case MIMETYPE_APPLICATION_ACTIVITY:
+                i.putExtra(ClipDescription.EXTRA_PENDING_INTENT, mock(PendingIntent.class));
+                break;
+        }
+        i.putExtra(Intent.EXTRA_USER, android.os.Process.myUserHandle());
+        ClipData.Item item = new ClipData.Item(i);
+        item.setActivityInfo(new ActivityInfo());
+        ClipData data = new ClipData(clipDescription, item);
+        setClipDataResizeable(data, true);
+        return data;
+    }
+
+    private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType) {
+        ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+        info.configuration.windowConfiguration.setActivityType(actType);
+        info.configuration.windowConfiguration.setWindowingMode(winMode);
+        info.isResizeable = true;
+        return info;
+    }
+
+    private void setRunningTask(ActivityManager.RunningTaskInfo task) throws RemoteException {
+        doReturn(Collections.singletonList(task)).when(mIActivityTaskManager)
+                .getFilteredTasks(anyInt(), anyBoolean());
+    }
+
+    private void setClipDataResizeable(ClipData data, boolean resizeable) {
+        data.getItemAt(0).getActivityInfo().resizeMode = resizeable
+                ? ActivityInfo.RESIZE_MODE_RESIZEABLE
+                : ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+    }
+
+    private void setIsPhone(boolean isPhone) {
+        Resources res = mock(Resources.class);
+        Configuration config = mock(Configuration.class);
+        config.smallestScreenWidthDp = isPhone ? 400 : 800;
+        doReturn(config).when(res).getConfiguration();
+        doReturn(res).when(mContext).getResources();
+    }
+
+    private void setInSplitScreen(boolean inSplitscreen) {
+        doReturn(inSplitscreen).when(mSplitScreen).isDividerVisible();
+    }
+
+    @Test
+    public void testDragAppOverFullscreenHome_expectOnlyFullscreenTarget() throws RemoteException {
+        setRunningTask(mHomeTask);
+        mPolicy.start(mDisplayLayout, mActivityClipData);
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+    }
+
+    @Test
+    public void testDragAppOverFullscreenApp_expectSplitScreenAndFullscreenTargets()
+            throws RemoteException {
+        setRunningTask(mFullscreenAppTask);
+        mPolicy.start(mDisplayLayout, mActivityClipData);
+        // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
+        //                    pairs
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_RIGHT);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+        reset(mStarter);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
+        verify(mStarter).enterSplitScreen(anyInt(), eq(false));
+        verify(mStarter).startIntent(any(), any());
+    }
+
+    @Test
+    public void testDragAppOverFullscreenAppPhone_expectVerticalSplitScreenAndFullscreenTargets()
+            throws RemoteException {
+        setIsPhone(true);
+        setRunningTask(mFullscreenAppTask);
+        mPolicy.start(mDisplayLayout, mActivityClipData);
+        // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
+        //                    pairs
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_BOTTOM);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+        reset(mStarter);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
+        verify(mStarter).enterSplitScreen(anyInt(), eq(false));
+        verify(mStarter).startIntent(any(), any());
+    }
+
+    @Test
+    public void testDragAppOverFullscreenNonResizeableApp_expectOnlyFullscreenTargets()
+            throws RemoteException {
+        setRunningTask(mNonResizeableFullscreenAppTask);
+        mPolicy.start(mDisplayLayout, mActivityClipData);
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+    }
+
+    @Test
+    public void testDragNonResizeableAppOverFullscreenApp_expectOnlyFullscreenTargets()
+            throws RemoteException {
+        setRunningTask(mFullscreenAppTask);
+        mPolicy.start(mDisplayLayout, mNonResizeableActivityClipData);
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+    }
+
+    @Test
+    public void testDragAppOverSplitApp_expectFullscreenAndSplitTargets() throws RemoteException {
+        setInSplitScreen(true);
+        setRunningTask(mSplitPrimaryAppTask);
+        mPolicy.start(mDisplayLayout, mActivityClipData);
+        // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
+        //                    pairs
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_RIGHT);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+        reset(mStarter);
+
+        // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_RIGHT), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+    }
+
+    @Test
+    public void testDragAppOverSplitAppPhone_expectFullscreenAndVerticalSplitTargets()
+            throws RemoteException {
+        setIsPhone(true);
+        setInSplitScreen(true);
+        setRunningTask(mSplitPrimaryAppTask);
+        mPolicy.start(mDisplayLayout, mActivityClipData);
+        // TODO(b/169894807): For now, only allow splitting to the right/bottom until we have split
+        //                    pairs
+        ArrayList<Target> targets = assertExactTargetTypes(
+                mPolicy.getTargets(mInsets), TYPE_FULLSCREEN, TYPE_SPLIT_BOTTOM);
+
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+        reset(mStarter);
+
+        // TODO(b/169894807): Just verify starting for the non-docked task until we have app pairs
+        mPolicy.handleDrop(filterTargetByType(targets, TYPE_SPLIT_BOTTOM), mActivityClipData);
+        verify(mStarter).startIntent(any(), any());
+    }
+
+    @Test
+    public void testTargetHitRects() throws RemoteException {
+        setRunningTask(mFullscreenAppTask);
+        mPolicy.start(mDisplayLayout, mActivityClipData);
+        ArrayList<Target> targets = mPolicy.getTargets(mInsets);
+        for (Target t : targets) {
+            assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.top) == t);
+            assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.right - 1, t.hitRegion.top) == t);
+            assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.right - 1, t.hitRegion.bottom - 1)
+                    == t);
+            assertTrue(mPolicy.getTargetAtLocation(t.hitRegion.left, t.hitRegion.bottom - 1)
+                    == t);
+        }
+    }
+
+    private Target filterTargetByType(ArrayList<Target> targets, int type) {
+        for (Target t : targets) {
+            if (type == t.type) {
+                return t;
+            }
+        }
+        fail("Target with type: " + type + " not found");
+        return null;
+    }
+
+    private ArrayList<Target> assertExactTargetTypes(ArrayList<Target> targets,
+            int... expectedTargetTypes) {
+        HashSet<Integer> expected = new HashSet<>();
+        for (int t : expectedTargetTypes) {
+            expected.add(t);
+        }
+        for (Target t : targets) {
+            if (!expected.contains(t.type)) {
+                fail("Found unexpected target type: " + t.type);
+            }
+            expected.remove(t.type);
+        }
+        assertTrue(expected.isEmpty());
+        return targets;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index 255e749..55e7a35 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -32,9 +32,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
-import com.android.wm.shell.pip.PipTestCase;
+import com.android.wm.shell.ShellTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -49,7 +47,7 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipAnimationControllerTest extends PipTestCase {
+public class PipAnimationControllerTest extends ShellTestCase {
 
     private PipAnimationController mPipAnimationController;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
index 37421d9..5169243 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsHandlerTest.java
@@ -30,8 +30,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.pip.PipBoundsHandler;
-import com.android.wm.shell.pip.PipTestCase;
+import com.android.wm.shell.ShellTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -46,7 +45,7 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipBoundsHandlerTest extends PipTestCase {
+public class PipBoundsHandlerTest extends ShellTestCase {
     private static final int ROUNDING_ERROR_MARGIN = 16;
     private static final float ASPECT_RATIO_ERROR_MARGIN = 0.01f;
     private static final float DEFAULT_ASPECT_RATIO = 1f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index dc9399e..844f82d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -28,6 +28,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.ShellTestCase;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,7 +40,7 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 @SmallTest
-public class PipBoundsStateTest extends PipTestCase {
+public class PipBoundsStateTest extends ShellTestCase {
 
     private static final Rect DEFAULT_BOUNDS = new Rect(0, 0, 10, 10);
     private static final float DEFAULT_SNAP_FRACTION = 1.0f;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 39381c6..efe553a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -40,6 +40,7 @@
 import android.window.WindowContainerToken;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.pip.phone.PipMenuActivityController;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -58,7 +59,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class PipTaskOrganizerTest extends PipTestCase {
+public class PipTaskOrganizerTest extends ShellTestCase {
     private PipTaskOrganizer mSpiedPipTaskOrganizer;
 
     @Mock private DisplayController mMockdDisplayController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 5f0f196..a00a3b6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -35,6 +35,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
@@ -42,7 +43,6 @@
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -56,7 +56,7 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class PipControllerTest extends PipTestCase {
+public class PipControllerTest extends ShellTestCase {
     private PipController mPipController;
 
     @Mock private DisplayController mMockDisplayController;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 3f60cc0..f6dcec2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -31,17 +31,13 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.pip.PipBoundsHandler;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
-import com.android.wm.shell.pip.PipTestCase;
 import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipMenuActivityController;
-import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipResizeGestureHandler;
-import com.android.wm.shell.pip.phone.PipTouchHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -59,7 +55,7 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class PipTouchHandlerTest extends PipTestCase {
+public class PipTouchHandlerTest extends ShellTestCase {
 
     private PipTouchHandler mPipTouchHandler;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
index 40667f7..000f7e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchStateTest.java
@@ -35,8 +35,7 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.wm.shell.pip.PipTestCase;
-import com.android.wm.shell.pip.phone.PipTouchState;
+import com.android.wm.shell.ShellTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,7 +46,7 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 @RunWithLooper
-public class PipTouchStateTest extends PipTestCase {
+public class PipTouchStateTest extends ShellTestCase {
 
     private PipTouchState mTouchState;
     private CountDownLatch mDoubleTapCallbackTriggeredLatch;
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 903ca2a..a3fcf90 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -160,10 +160,17 @@
                 "tests/ObbFile_test.cpp",
                 "tests/PosixUtils_test.cpp",
             ],
-            shared_libs: common_test_libs + ["libbinder", "liblog", "libui"],
+            shared_libs: common_test_libs + [
+                "libbinder",
+                "liblog",
+                "libui",
+            ],
         },
         host: {
-            static_libs: common_test_libs + ["liblog", "libz"],
+            static_libs: common_test_libs + [
+                "liblog",
+                "libz",
+            ],
         },
     },
     data: [
@@ -204,10 +211,20 @@
     export_include_dirs: ["include"],
     target: {
         android: {
-            shared_libs: common_test_libs + ["libbinder", "liblog"],
+            shared_libs: common_test_libs + [
+                "libbinder",
+                "liblog",
+            ],
         },
         host: {
-            static_libs: common_test_libs + ["libbinder", "liblog"],
+            static_libs: common_test_libs + [
+                "libbinder",
+                "liblog",
+            ],
+        },
+        darwin: {
+            // libbinder is not supported on mac
+            enabled: false,
         },
     },
 }
diff --git a/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp
index 2dac47b..b36ff09 100644
--- a/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp
+++ b/libs/androidfw/fuzz/cursorwindow_fuzzer/Android.bp
@@ -27,5 +27,9 @@
                 "libutils",
             ],
         },
+        darwin: {
+            // libbinder is not supported on mac
+            enabled: false,
+        },
     },
 }
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 3493693..b61b79e 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -76,12 +76,11 @@
  * obtain periodic updates of the device's geographical location, or to be notified when the device
  * enters the proximity of a given geographical location.
  *
- * <p class="note">Unless noted, all Location API methods require the {@link
- * android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link
- * android.Manifest.permission#ACCESS_FINE_LOCATION} permissions. If your application only has the
- * coarse permission then it will not have access to fine location providers. Other providers will
- * still return location results, but the exact location will be obfuscated to a coarse level of
- * accuracy.
+ * <p class="note">Unless otherwise noted, all Location API methods require the
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or
+ * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permissions. If your application only
+ * has the coarse permission then providers will still return location results, but the exact
+ * location will be obfuscated to a coarse level of accuracy.
  */
 @SuppressWarnings({"deprecation"})
 @SystemService(Context.LOCATION_SERVICE)
@@ -89,6 +88,16 @@
 public class LocationManager {
 
     /**
+     * For apps targeting Android S and above, immutable PendingIntents passed into location APIs
+     * will generate an IllegalArgumentException.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    public static final long BLOCK_IMMUTABLE_PENDING_INTENTS = 171317480L;
+
+    /**
      * For apps targeting Android S and above, LocationRequest system APIs may not be used with
      * PendingIntent location requests.
      *
@@ -96,7 +105,7 @@
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
-    public static final long PREVENT_PENDING_INTENT_SYSTEM_API_USAGE = 169887240L;
+    public static final long BLOCK_PENDING_INTENT_SYSTEM_API_USAGE = 169887240L;
 
     /**
      * For apps targeting Android S and above, location clients may receive historical locations
@@ -116,7 +125,7 @@
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
-    private static final long GET_PROVIDER_SECURITY_EXCEPTIONS = 150935354L;
+    public static final long GET_PROVIDER_SECURITY_EXCEPTIONS = 150935354L;
 
     /**
      * For apps targeting Android K and above, supplied {@link PendingIntent}s must be targeted to a
@@ -126,7 +135,7 @@
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
-    private static final long TARGETED_PENDING_INTENT = 148963590L;
+    public static final long BLOCK_UNTARGETED_PENDING_INTENTS = 148963590L;
 
     /**
      * For apps targeting Android K and above, incomplete locations may not be passed to
@@ -136,7 +145,7 @@
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN)
-    private static final long INCOMPLETE_LOCATION = 148964793L;
+    public static final long BLOCK_INCOMPLETE_LOCATIONS = 148964793L;
 
     /**
      * For apps targeting Android S and above, all {@link GpsStatus} API usage must be replaced with
@@ -146,7 +155,7 @@
      */
     @ChangeId
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
-    private static final long GPS_STATUS_USAGE = 144027538L;
+    public static final long BLOCK_GPS_STATUS_USAGE = 144027538L;
 
     /**
      * Name of the network location provider.
@@ -1372,11 +1381,16 @@
         Preconditions.checkArgument(locationRequest != null, "invalid null location request");
         Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
 
-        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
+        if (Compatibility.isChangeEnabled(BLOCK_UNTARGETED_PENDING_INTENTS)) {
             Preconditions.checkArgument(pendingIntent.isTargetedToPackage(),
                     "pending intent must be targeted to a package");
         }
 
+        if (Compatibility.isChangeEnabled(BLOCK_IMMUTABLE_PENDING_INTENTS)) {
+            Preconditions.checkArgument(!pendingIntent.isImmutable(),
+                    "pending intent must be mutable");
+        }
+
         try {
             mService.registerLocationPendingIntent(provider, locationRequest, pendingIntent,
                     mContext.getPackageName(), mContext.getAttributionTag());
@@ -1729,7 +1743,7 @@
         Preconditions.checkArgument(provider != null, "invalid null provider");
         Preconditions.checkArgument(location != null, "invalid null location");
 
-        if (Compatibility.isChangeEnabled(INCOMPLETE_LOCATION)) {
+        if (Compatibility.isChangeEnabled(BLOCK_INCOMPLETE_LOCATIONS)) {
             Preconditions.checkArgument(location.isComplete(),
                     "incomplete location object, missing timestamp or accuracy?");
         } else {
@@ -1821,29 +1835,38 @@
      * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. From API version 17 and onwards,
      * this method requires {@link android.Manifest.permission#ACCESS_FINE_LOCATION} permission.
      *
-     * @param latitude   the latitude of the central point of the alert region
-     * @param longitude  the longitude of the central point of the alert region
-     * @param radius     the radius of the central point of the alert region in meters
-     * @param expiration expiration realtime for this proximity alert in milliseconds, or -1 to
-     *                   indicate no expiration
-     * @param intent     a {@link PendingIntent} that will sent when entry to or exit from the alert
-     *                   region is detected
+     * @param latitude      the latitude of the central point of the alert region
+     * @param longitude     the longitude of the central point of the alert region
+     * @param radius        the radius of the central point of the alert region in meters
+     * @param expiration    expiration realtime for this proximity alert in milliseconds, or -1 to
+     *                      indicate no expiration
+     * @param pendingIntent a {@link PendingIntent} that will sent when entry to or exit from the
+     *                      alert region is detected
      * @throws SecurityException if {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
      *                           permission is not present
      */
     @RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
     public void addProximityAlert(double latitude, double longitude, float radius, long expiration,
-            @NonNull PendingIntent intent) {
-        Preconditions.checkArgument(intent != null, "invalid null pending intent");
-        if (Compatibility.isChangeEnabled(TARGETED_PENDING_INTENT)) {
-            Preconditions.checkArgument(intent.isTargetedToPackage(),
+            @NonNull PendingIntent pendingIntent) {
+        Preconditions.checkArgument(pendingIntent != null, "invalid null pending intent");
+
+        if (Compatibility.isChangeEnabled(BLOCK_UNTARGETED_PENDING_INTENTS)) {
+            Preconditions.checkArgument(pendingIntent.isTargetedToPackage(),
                     "pending intent must be targeted to a package");
         }
-        if (expiration < 0) expiration = Long.MAX_VALUE;
+
+        if (Compatibility.isChangeEnabled(BLOCK_IMMUTABLE_PENDING_INTENTS)) {
+            Preconditions.checkArgument(!pendingIntent.isImmutable(),
+                    "pending intent must be mutable");
+        }
+
+        if (expiration < 0) {
+            expiration = Long.MAX_VALUE;
+        }
 
         try {
             Geofence fence = Geofence.createCircle(latitude, longitude, radius, expiration);
-            mService.requestGeofence(fence, intent, mContext.getPackageName(),
+            mService.requestGeofence(fence, pendingIntent, mContext.getPackageName(),
                     mContext.getAttributionTag());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1941,7 +1964,7 @@
     @Deprecated
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public @Nullable GpsStatus getGpsStatus(@Nullable GpsStatus status) {
-        if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) {
+        if (Compatibility.isChangeEnabled(BLOCK_GPS_STATUS_USAGE)) {
             throw new UnsupportedOperationException(
                     "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
@@ -1976,7 +1999,7 @@
     @Deprecated
     @RequiresPermission(ACCESS_FINE_LOCATION)
     public boolean addGpsStatusListener(GpsStatus.Listener listener) {
-        if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) {
+        if (Compatibility.isChangeEnabled(BLOCK_GPS_STATUS_USAGE)) {
             throw new UnsupportedOperationException(
                     "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
@@ -1996,7 +2019,7 @@
      */
     @Deprecated
     public void removeGpsStatusListener(GpsStatus.Listener listener) {
-        if (Compatibility.isChangeEnabled(GPS_STATUS_USAGE)) {
+        if (Compatibility.isChangeEnabled(BLOCK_GPS_STATUS_USAGE)) {
             throw new UnsupportedOperationException(
                     "GpsStatus APIs not supported, please use GnssStatus APIs instead");
         }
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 4b208ce..5c1f893 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -676,17 +676,9 @@
         return null;
     }
 
-    /**
-     * Instantiate a CA system of the specified system id.
-     *
-     * @param CA_system_id The system id of the CA system.
-     *
-     * @throws UnsupportedCasException if the device does not support the
-     * specified CA system.
-     */
-    public MediaCas(int CA_system_id) throws UnsupportedCasException {
+    private void createPlugin(int casSystemId) throws UnsupportedCasException {
         try {
-            mCasSystemId = CA_system_id;
+            mCasSystemId = casSystemId;
             mUserId = ActivityManager.getCurrentUser();
             IMediaCasService service = getService();
             android.hardware.cas.V1_2.IMediaCasService serviceV12 =
@@ -696,16 +688,16 @@
                     android.hardware.cas.V1_1.IMediaCasService.castFrom(service);
                 if (serviceV11 == null) {
                     Log.d(TAG, "Used cas@1_0 interface to create plugin");
-                    mICas = service.createPlugin(CA_system_id, mBinder);
+                    mICas = service.createPlugin(casSystemId, mBinder);
                 } else {
                     Log.d(TAG, "Used cas@1.1 interface to create plugin");
-                    mICas = mICasV11 = serviceV11.createPluginExt(CA_system_id, mBinder);
+                    mICas = mICasV11 = serviceV11.createPluginExt(casSystemId, mBinder);
                 }
             } else {
                 Log.d(TAG, "Used cas@1.2 interface to create plugin");
                 mICas = mICasV11 = mICasV12 =
                     android.hardware.cas.V1_2.ICas
-                    .castFrom(serviceV12.createPluginExt(CA_system_id, mBinder));
+                        .castFrom(serviceV12.createPluginExt(casSystemId, mBinder));
             }
         } catch(Exception e) {
             Log.e(TAG, "Failed to create plugin: " + e);
@@ -713,11 +705,37 @@
         } finally {
             if (mICas == null) {
                 throw new UnsupportedCasException(
-                        "Unsupported CA_system_id " + CA_system_id);
+                    "Unsupported casSystemId " + casSystemId);
             }
         }
     }
 
+    private void registerClient(@NonNull Context context,
+            @Nullable String tvInputServiceSessionId,  @PriorityHintUseCaseType int priorityHint)  {
+
+        mTunerResourceManager = (TunerResourceManager)
+            context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+        if (mTunerResourceManager != null) {
+            int[] clientId = new int[1];
+            ResourceClientProfile profile =
+                    new ResourceClientProfile(tvInputServiceSessionId, priorityHint);
+            mTunerResourceManager.registerClientProfile(
+                    profile, context.getMainExecutor(), mResourceListener, clientId);
+            mClientId = clientId[0];
+        }
+    }
+    /**
+     * Instantiate a CA system of the specified system id.
+     *
+     * @param casSystemId The system id of the CA system.
+     *
+     * @throws UnsupportedCasException if the device does not support the
+     * specified CA system.
+     */
+    public MediaCas(int casSystemId) throws UnsupportedCasException {
+        createPlugin(casSystemId);
+    }
+
     /**
      * Instantiate a CA system of the specified system id.
      *
@@ -733,19 +751,35 @@
     public MediaCas(@NonNull Context context, int casSystemId,
             @Nullable String tvInputServiceSessionId,
             @PriorityHintUseCaseType int priorityHint) throws UnsupportedCasException {
-        this(casSystemId);
-
         Objects.requireNonNull(context, "context must not be null");
-        mTunerResourceManager = (TunerResourceManager)
-                context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
-        if (mTunerResourceManager != null) {
-            int[] clientId = new int[1];
-            ResourceClientProfile profile =
-                    new ResourceClientProfile(tvInputServiceSessionId, priorityHint);
-            mTunerResourceManager.registerClientProfile(
-                    profile, context.getMainExecutor(), mResourceListener, clientId);
-            mClientId = clientId[0];
-        }
+        createPlugin(casSystemId);
+        registerClient(context, tvInputServiceSessionId, priorityHint);
+    }
+    /**
+     * Instantiate a CA system of the specified system id with EvenListener.
+     *
+     * @param context the context of the caller.
+     * @param casSystemId The system id of the CA system.
+     * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS)
+     *        {@link android.media.tv.TvInputService#onCreateSession(String, String)}
+     * @param priorityHint priority hint from the use case type for new created CAS system.
+     * @param listener the event listener to be set.
+     * @param handler the handler whose looper the event listener will be called on.
+     * If handler is null, we'll try to use current thread's looper, or the main
+     * looper. If neither are available, an internal thread will be created instead.
+     *
+     * @throws UnsupportedCasException if the device does not support the
+     * specified CA system.
+     */
+    public MediaCas(@NonNull Context context, int casSystemId,
+            @Nullable String tvInputServiceSessionId,
+            @PriorityHintUseCaseType int priorityHint,
+            @Nullable Handler handler, @Nullable EventListener listener)
+            throws UnsupportedCasException {
+        Objects.requireNonNull(context, "context must not be null");
+        setEventListener(listener, handler);
+        createPlugin(casSystemId);
+        registerClient(context, tvInputServiceSessionId, priorityHint);
     }
 
     IHwBinder getBinder() {
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index df5e85e..da69c6c 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -520,11 +520,12 @@
 
         @Override
         public void applyVolumeShaper(
-                @NonNull VolumeShaper.Configuration configuration,
-                @NonNull VolumeShaper.Operation operation) {
+                @NonNull VolumeShaperConfiguration configuration,
+                @NonNull VolumeShaperOperation operation) {
             final PlayerBase pb = mWeakPB.get();
             if (pb != null) {
-                pb.playerApplyVolumeShaper(configuration, operation);
+                pb.playerApplyVolumeShaper(VolumeShaper.Configuration.fromParcelable(configuration),
+                        VolumeShaper.Operation.fromParcelable(operation));
             }
         }
     }
diff --git a/media/java/android/media/PlayerProxy.java b/media/java/android/media/PlayerProxy.java
index 5f3997a..ec39128 100644
--- a/media/java/android/media/PlayerProxy.java
+++ b/media/java/android/media/PlayerProxy.java
@@ -143,7 +143,8 @@
             @NonNull VolumeShaper.Configuration configuration,
             @NonNull VolumeShaper.Operation operation) {
         try {
-            mConf.getIPlayer().applyVolumeShaper(configuration, operation);
+            mConf.getIPlayer().applyVolumeShaper(configuration.toParcelable(),
+                    operation.toParcelable());
         } catch (NullPointerException|RemoteException e) {
             throw new IllegalStateException(
                     "No player to proxy for applyVolumeShaper operation,"
diff --git a/media/java/android/media/VolumeShaper.java b/media/java/android/media/VolumeShaper.java
index df8d08e..5bad693 100644
--- a/media/java/android/media/VolumeShaper.java
+++ b/media/java/android/media/VolumeShaper.java
@@ -21,6 +21,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.os.BadParcelableException;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -482,50 +483,62 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            // this needs to match the native VolumeShaper.Configuration parceling
-            dest.writeInt(mType);
-            dest.writeInt(mId);
-            if (mType != TYPE_ID) {
-                dest.writeInt(mOptionFlags);
-                dest.writeDouble(mDurationMs);
-                // this needs to match the native Interpolator parceling
-                dest.writeInt(mInterpolatorType);
-                dest.writeFloat(0.f); // first slope (specifying for native side)
-                dest.writeFloat(0.f); // last slope (specifying for native side)
-                // mTimes and mVolumes should have the same length.
-                dest.writeInt(mTimes.length);
-                for (int i = 0; i < mTimes.length; ++i) {
-                    dest.writeFloat(mTimes[i]);
-                    dest.writeFloat(mVolumes[i]);
-                }
-            }
+            VolumeShaperConfiguration parcelable = toParcelable();
+            parcelable.writeToParcel(dest, flags);
         }
 
-        public static final @android.annotation.NonNull Parcelable.Creator<VolumeShaper.Configuration> CREATOR
-                = new Parcelable.Creator<VolumeShaper.Configuration>() {
-            @Override
-            public VolumeShaper.Configuration createFromParcel(Parcel p) {
-                // this needs to match the native VolumeShaper.Configuration parceling
-                final int type = p.readInt();
-                final int id = p.readInt();
-                if (type == TYPE_ID) {
-                    return new VolumeShaper.Configuration(id);
-                } else {
-                    final int optionFlags = p.readInt();
-                    final double durationMs = p.readDouble();
-                    // this needs to match the native Interpolator parceling
-                    final int interpolatorType = p.readInt();
-                    final float firstSlope = p.readFloat(); // ignored on the Java side
-                    final float lastSlope = p.readFloat();  // ignored on the Java side
-                    final int length = p.readInt();
-                    final float[] times = new float[length];
-                    final float[] volumes = new float[length];
-                    for (int i = 0; i < length; ++i) {
-                        times[i] = p.readFloat();
-                        volumes[i] = p.readFloat();
-                    }
+        /** @hide */
+        public VolumeShaperConfiguration toParcelable() {
+            VolumeShaperConfiguration parcelable = new VolumeShaperConfiguration();
+            parcelable.type = typeToAidl(mType);
+            parcelable.id = mId;
+            if (mType != TYPE_ID) {
+                parcelable.optionFlags = optionFlagsToAidl(mOptionFlags);
+                parcelable.durationMs = mDurationMs;
+                parcelable.interpolatorConfig = toInterpolatorParcelable();
+            }
+            return parcelable;
+        }
 
-                    return new VolumeShaper.Configuration(
+        private InterpolatorConfig toInterpolatorParcelable() {
+            InterpolatorConfig parcelable = new InterpolatorConfig();
+            parcelable.type = interpolatorTypeToAidl(mInterpolatorType);
+            parcelable.firstSlope = 0.f; // first slope (specifying for native side)
+            parcelable.lastSlope = 0.f; // last slope (specifying for native side)
+            parcelable.xy = new float[mTimes.length * 2];
+            for (int i = 0; i < mTimes.length; ++i) {
+                parcelable.xy[i * 2] = mTimes[i];
+                parcelable.xy[i * 2 + 1] = mVolumes[i];
+            }
+            return parcelable;
+        }
+
+        /** @hide */
+        public static Configuration fromParcelable(VolumeShaperConfiguration parcelable) {
+            // this needs to match the native VolumeShaper.Configuration parceling
+            final int type = typeFromAidl(parcelable.type);
+            final int id = parcelable.id;
+            if (type == TYPE_ID) {
+                return new VolumeShaper.Configuration(id);
+            } else {
+                final int optionFlags = optionFlagsFromAidl(parcelable.optionFlags);
+                final double durationMs = parcelable.durationMs;
+                final int interpolatorType = interpolatorTypeFromAidl(
+                        parcelable.interpolatorConfig.type);
+                // parcelable.interpolatorConfig.firstSlope is ignored on the Java side
+                // parcelable.interpolatorConfig.lastSlope is ignored on the Java side
+                final int length = parcelable.interpolatorConfig.xy.length;
+                if (length % 2 != 0) {
+                    throw new android.os.BadParcelableException("xy length must be even");
+                }
+                final float[] times = new float[length / 2];
+                final float[] volumes = new float[length / 2];
+                for (int i = 0; i < length / 2; ++i) {
+                    times[i] = parcelable.interpolatorConfig.xy[i * 2];
+                    volumes[i] = parcelable.interpolatorConfig.xy[i * 2 + 1];
+                }
+
+                return new VolumeShaper.Configuration(
                         type,
                         id,
                         optionFlags,
@@ -533,7 +546,14 @@
                         interpolatorType,
                         times,
                         volumes);
-                }
+            }
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<VolumeShaper.Configuration> CREATOR
+                = new Parcelable.Creator<VolumeShaper.Configuration>() {
+            @Override
+            public VolumeShaper.Configuration createFromParcel(Parcel p) {
+                return fromParcelable(VolumeShaperConfiguration.CREATOR.createFromParcel(p));
             }
 
             @Override
@@ -542,6 +562,84 @@
             }
         };
 
+        private static @InterpolatorType
+        int interpolatorTypeFromAidl(@android.media.InterpolatorType int aidl) {
+            switch (aidl) {
+                case android.media.InterpolatorType.STEP:
+                    return INTERPOLATOR_TYPE_STEP;
+                case android.media.InterpolatorType.LINEAR:
+                    return INTERPOLATOR_TYPE_LINEAR;
+                case android.media.InterpolatorType.CUBIC:
+                    return INTERPOLATOR_TYPE_CUBIC;
+                case android.media.InterpolatorType.CUBIC_MONOTONIC:
+                    return INTERPOLATOR_TYPE_CUBIC_MONOTONIC;
+                default:
+                    throw new BadParcelableException("Unknown interpolator type");
+            }
+        }
+
+        private static @android.media.InterpolatorType
+        int interpolatorTypeToAidl(@InterpolatorType int type) {
+            switch (type) {
+                case INTERPOLATOR_TYPE_STEP:
+                    return android.media.InterpolatorType.STEP;
+                case INTERPOLATOR_TYPE_LINEAR:
+                    return android.media.InterpolatorType.LINEAR;
+                case INTERPOLATOR_TYPE_CUBIC:
+                    return android.media.InterpolatorType.CUBIC;
+                case INTERPOLATOR_TYPE_CUBIC_MONOTONIC:
+                    return android.media.InterpolatorType.CUBIC_MONOTONIC;
+                default:
+                    throw new RuntimeException("Unknown interpolator type");
+            }
+        }
+
+        private static @Type
+        int typeFromAidl(@android.media.VolumeShaperConfigurationType int aidl) {
+            switch (aidl) {
+                case VolumeShaperConfigurationType.ID:
+                    return TYPE_ID;
+                case VolumeShaperConfigurationType.SCALE:
+                    return TYPE_SCALE;
+                default:
+                    throw new BadParcelableException("Unknown type");
+            }
+        }
+
+        private static @android.media.VolumeShaperConfigurationType
+        int typeToAidl(@Type int type) {
+            switch (type) {
+                case TYPE_ID:
+                    return VolumeShaperConfigurationType.ID;
+                case TYPE_SCALE:
+                    return VolumeShaperConfigurationType.SCALE;
+                default:
+                    throw new RuntimeException("Unknown type");
+            }
+        }
+
+        private static int optionFlagsFromAidl(int aidl) {
+            int result = 0;
+            if ((aidl & (1 << VolumeShaperConfigurationOptionFlag.VOLUME_IN_DBFS)) != 0) {
+                result |= OPTION_FLAG_VOLUME_IN_DBFS;
+            }
+            if ((aidl & (1 << VolumeShaperConfigurationOptionFlag.CLOCK_TIME)) != 0) {
+                result |= OPTION_FLAG_CLOCK_TIME;
+            }
+            return result;
+        }
+
+        private static int optionFlagsToAidl(int flags) {
+            int result = 0;
+            if ((flags & OPTION_FLAG_VOLUME_IN_DBFS) != 0) {
+                result |= (1 << VolumeShaperConfigurationOptionFlag.VOLUME_IN_DBFS);
+            }
+            if ((flags & OPTION_FLAG_CLOCK_TIME) != 0) {
+                result |= (1 << VolumeShaperConfigurationOptionFlag.CLOCK_TIME);
+            }
+            return result;
+        }
+
         /**
          * @hide
          * Constructs a {@code VolumeShaper} from an id.
@@ -1172,25 +1270,31 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            // this needs to match the native VolumeShaper.Operation parceling
-            dest.writeInt(mFlags);
-            dest.writeInt(mReplaceId);
-            dest.writeFloat(mXOffset);
+            toParcelable().writeToParcel(dest, flags);
+        }
+
+        /** @hide */
+        public VolumeShaperOperation toParcelable() {
+            VolumeShaperOperation result = new VolumeShaperOperation();
+            result.flags = flagsToAidl(mFlags);
+            result.replaceId = mReplaceId;
+            result.xOffset = mXOffset;
+            return result;
+        }
+
+        /** @hide */
+        public static Operation fromParcelable(VolumeShaperOperation parcelable) {
+            return new VolumeShaper.Operation(
+                    flagsFromAidl(parcelable.flags),
+                    parcelable.replaceId,
+                    parcelable.xOffset);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<VolumeShaper.Operation> CREATOR
                 = new Parcelable.Creator<VolumeShaper.Operation>() {
             @Override
             public VolumeShaper.Operation createFromParcel(Parcel p) {
-                // this needs to match the native VolumeShaper.Operation parceling
-                final int flags = p.readInt();
-                final int replaceId = p.readInt();
-                final float xOffset = p.readFloat();
-
-                return new VolumeShaper.Operation(
-                        flags
-                        , replaceId
-                        , xOffset);
+                return fromParcelable(VolumeShaperOperation.CREATOR.createFromParcel(p));
             }
 
             @Override
@@ -1199,6 +1303,46 @@
             }
         };
 
+        private static int flagsFromAidl(int aidl) {
+            int result = 0;
+            if ((aidl & (1 << VolumeShaperOperationFlag.REVERSE)) != 0) {
+                result |= FLAG_REVERSE;
+            }
+            if ((aidl & (1 << VolumeShaperOperationFlag.TERMINATE)) != 0) {
+                result |= FLAG_TERMINATE;
+            }
+            if ((aidl & (1 << VolumeShaperOperationFlag.JOIN)) != 0) {
+                result |= FLAG_JOIN;
+            }
+            if ((aidl & (1 << VolumeShaperOperationFlag.DELAY)) != 0) {
+                result |= FLAG_DEFER;
+            }
+            if ((aidl & (1 << VolumeShaperOperationFlag.CREATE_IF_NECESSARY)) != 0) {
+                result |= FLAG_CREATE_IF_NEEDED;
+            }
+            return result;
+        }
+
+        private static int flagsToAidl(int flags) {
+            int result = 0;
+            if ((flags & FLAG_REVERSE) != 0) {
+                result |= (1 << VolumeShaperOperationFlag.REVERSE);
+            }
+            if ((flags & FLAG_TERMINATE) != 0) {
+                result |= (1 << VolumeShaperOperationFlag.TERMINATE);
+            }
+            if ((flags & FLAG_JOIN) != 0) {
+                result |= (1 << VolumeShaperOperationFlag.JOIN);
+            }
+            if ((flags & FLAG_DEFER) != 0) {
+                result |= (1 << VolumeShaperOperationFlag.DELAY);
+            }
+            if ((flags & FLAG_CREATE_IF_NEEDED) != 0) {
+                result |= (1 << VolumeShaperOperationFlag.CREATE_IF_NECESSARY);
+            }
+            return result;
+        }
+
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         private Operation(@Flag int flags, int replaceId, float xOffset) {
             mFlags = flags;
@@ -1393,17 +1537,27 @@
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeFloat(mVolume);
-            dest.writeFloat(mXOffset);
+            toParcelable().writeToParcel(dest, flags);
+        }
+
+        /** @hide */
+        public VolumeShaperState toParcelable() {
+            VolumeShaperState result = new VolumeShaperState();
+            result.volume = mVolume;
+            result.xOffset = mXOffset;
+            return result;
+        }
+
+        /** @hide */
+        public static State fromParcelable(VolumeShaperState p) {
+            return new VolumeShaper.State(p.volume, p.xOffset);
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<VolumeShaper.State> CREATOR
                 = new Parcelable.Creator<VolumeShaper.State>() {
             @Override
             public VolumeShaper.State createFromParcel(Parcel p) {
-                return new VolumeShaper.State(
-                        p.readFloat()     // volume
-                        , p.readFloat()); // xOffset
+                return fromParcelable(VolumeShaperState.CREATOR.createFromParcel(p));
             }
 
             @Override
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 1881e38..5a578dd 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -122,6 +122,17 @@
             android.hardware.tv.tuner.V1_1.Constants.Constant
                     .INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM;
     /**
+     * Invalid first macroblock address in MmtpRecordEvent and TsRecordEvent.
+     *
+     * <p>Returned by {@link MmtpRecordEvent#getMbInSlice()} and
+     * {@link TsRecordEvent#getMbInSlice()} when the requested sequence number is not available.
+     *
+     * @see android.media.tv.tuner.filter.MmtpRecordEvent#getMbInSlice()
+     * @see android.media.tv.tuner.filter.TsRecordEvent#getMbInSlice()
+     */
+    public static final int INVALID_FIRST_MACROBLOCK_IN_SLICE =
+            android.hardware.tv.tuner.V1_1.Constants.Constant.INVALID_FIRST_MACROBLOCK_IN_SLICE;
+    /**
      * Invalid local transport stream id.
      *
      * <p>Returned by {@link #linkFrontendToCiCam(int)} when the requested failed
@@ -1062,6 +1073,13 @@
         }
     }
 
+    private void onDvbcAnnexReported(int dvbcAnnex) {
+        if (mScanCallbackExecutor != null && mScanCallback != null) {
+            mScanCallbackExecutor.execute(
+                    () -> mScanCallback.onDvbcAnnexReported(dvbcAnnex));
+        }
+    }
+
     /**
      * Opens a filter object based on the given types and buffer size.
      *
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
index 7060bd72..f0abce9 100644
--- a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -31,13 +31,16 @@
     private final long mDataLength;
     private final int mMpuSequenceNumber;
     private final long mPts;
+    private final int mFirstMbInSlice;
 
     // This constructor is used by JNI code only
-    private MmtpRecordEvent(int scHevcIndexMask, long dataLength, int mpuSequenceNumber, long pts) {
+    private MmtpRecordEvent(int scHevcIndexMask, long dataLength, int mpuSequenceNumber, long pts,
+            int firstMbInSlice) {
         mScHevcIndexMask = scHevcIndexMask;
         mDataLength = dataLength;
         mMpuSequenceNumber = mpuSequenceNumber;
         mPts = pts;
+        mFirstMbInSlice = firstMbInSlice;
     }
 
     /**
@@ -58,6 +61,11 @@
 
     /**
      * Get the MPU sequence number of the filtered data.
+     *
+     * <p>This field is only supported in Tuner 1.1 or higher version. Unsupported version will
+     * return {@link android.media.tv.tuner.Tuner.INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM}. Use
+     * {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
+     * information.
      */
     public int getMpuSequenceNumber() {
         return mMpuSequenceNumber;
@@ -65,10 +73,26 @@
 
     /**
      * Get the Presentation Time Stamp(PTS) for the audio or video frame. It is based on 90KHz
-     * and has the same format as the PTS in ISO/IEC 13818-1. It is used only for the SC and
-     * the SC_HEVC.
+     * and has the same format as the PTS in ISO/IEC 13818-1.
+     *
+     * <p>This field is only supported in Tuner 1.1 or higher version. Unsupported version will
+     * return {@link android.media.tv.tuner.Tuner.INVALID_TIMESTAMP}. Use
+     * {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
+     * information.
      */
     public long getPts() {
         return mPts;
     }
+
+    /**
+     * Get the address of the first macroblock in the slice defined in ITU-T Rec. H.264.
+     *
+     * <p>This field is only supported in Tuner 1.1 or higher version. Unsupported version will
+     * return {@link android.media.tv.tuner.Tuner.INVALID_FIRST_MACROBLOCK_IN_SLICE}. Use
+     * {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
+     * information.
+     */
+    public int getFirstMbInSlice() {
+        return mFirstMbInSlice;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
index ec01e44..a6fd20e 100644
--- a/media/java/android/media/tv/tuner/filter/RecordSettings.java
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -133,8 +133,11 @@
      * according to ISO/IEC 13818-1.
      * @hide
      */
-    @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
-            SC_INDEX_SEQUENCE})
+    @IntDef(prefix = "SC_INDEX_",
+            flag = true,
+            value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
+                    SC_INDEX_SEQUENCE, SC_INDEX_I_SLICE, SC_INDEX_P_SLICE,
+                    SC_INDEX_B_SLICE, SC_INDEX_SI_SLICE, SC_INDEX_SP_SLICE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ScIndex {}
 
@@ -154,7 +157,31 @@
      * SC index for a new sequence.
      */
     public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE;
-
+    /**
+     * All blocks are coded as I blocks.
+     */
+    public static final int SC_INDEX_I_SLICE =
+            android.hardware.tv.tuner.V1_1.Constants.DemuxScIndex.I_SLICE;
+    /**
+     * Blocks are coded as I or P blocks.
+     */
+    public static final int SC_INDEX_P_SLICE =
+            android.hardware.tv.tuner.V1_1.Constants.DemuxScIndex.P_SLICE;
+    /**
+     * Blocks are coded as I, P or B blocks.
+     */
+    public static final int SC_INDEX_B_SLICE =
+            android.hardware.tv.tuner.V1_1.Constants.DemuxScIndex.B_SLICE;
+    /**
+     * A so-called switching I slice that is coded.
+     */
+    public static final int SC_INDEX_SI_SLICE =
+            android.hardware.tv.tuner.V1_1.Constants.DemuxScIndex.SI_SLICE;
+    /**
+     * A so-called switching P slice that is coded.
+     */
+    public static final int SC_INDEX_SP_SLICE =
+            android.hardware.tv.tuner.V1_1.Constants.DemuxScIndex.SP_SLICE;
 
     /**
      * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
index 258e2f2..6ea3bf8 100644
--- a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -33,14 +33,17 @@
     private final int mScIndexMask;
     private final long mDataLength;
     private final long mPts;
+    private final int mFirstMbInSlice;
 
     // This constructor is used by JNI code only
-    private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long dataLength, long pts) {
+    private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long dataLength, long pts,
+            int firstMbInSlice) {
         mPid = pid;
         mTsIndexMask = tsIndexMask;
         mScIndexMask = scIndexMask;
         mDataLength = dataLength;
         mPts = pts;
+        mFirstMbInSlice = firstMbInSlice;
     }
 
     /**
@@ -77,10 +80,26 @@
 
     /**
      * Gets the Presentation Time Stamp(PTS) for the audio or video frame. It is based on 90KHz
-     * and has the same format as the PTS in ISO/IEC 13818-1. It is used only for the SC and
-     * the SC_HEVC.
+     * and has the same format as the PTS in ISO/IEC 13818-1.
+     *
+     * <p>This field is only supported in Tuner 1.1 or higher version. Unsupported version will
+     * return {@link android.media.tv.tuner.Tuner.INVALID_TIMESTAMP}. Use
+     * {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
+     * information.
      */
     public long getPts() {
         return mPts;
     }
+
+    /**
+     * Get the address of the first macroblock in the slice defined in ITU-T Rec. H.264.
+     *
+     * <p>This field is only supported in Tuner 1.1 or higher version. Unsupported version will
+     * return {@link android.media.tv.tuner.Tuner.INVALID_FIRST_MACROBLOCK_IN_SLICE}. Use
+     * {@link android.media.tv.tuner.TunerVersionChecker.getTunerVersion()} to get the version
+     * information.
+     */
+    public int getFirstMbInSlice() {
+        return mFirstMbInSlice;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
index fadc004..98f8096 100644
--- a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -341,12 +341,13 @@
         return mScanType;
     }
     /**
-     * To receive Diseqc Message or not. Default value is false.
+     * Get if the client could handle the Diseqc Rx Message or not. Default value is false.
      *
-     * The setter {@link Builder#setDiseqcRxMessage(boolean)} is only supported with Tuner HAL 1.1
-     * or higher.
+     * The setter {@link Builder#setCouldHandleDiseqcRxMessage(boolean)} is only supported with
+     * Tuner HAL 1.1 or higher. Use {@link TunerVersionChecker.getTunerVersion()} to check the
+     * version.
      */
-    public boolean isDiseqcRxMessage() {
+    public boolean getCouldHandleDiseqcRxMessage() {
         return mIsDiseqcRxMessage;
     }
 
@@ -408,16 +409,18 @@
         }
 
         /**
-         * Set true to receive Diseqc Message.
+         * Set true to indicate the client could handle the Diseqc Messages. Note that it's still
+         * possible that the client won't receive the messages when HAL is not able to setup Rx
+         * channel in the hardware layer.
          *
          * <p>This API is only supported by Tuner HAL 1.1 or higher. Unsupported version would cause
          * no-op. Use {@link TunerVersionChecker.getTunerVersion()} to check the version.
          */
         @NonNull
-        public Builder setDiseqcRxMessage(boolean isDiseqcRxMessage) {
+        public Builder setCouldHandleDiseqcRxMessage(boolean couldReceiveDiseqcMessage) {
             if (TunerVersionChecker.checkHigherOrEqualVersionTo(
-                        TunerVersionChecker.TUNER_VERSION_1_1, "setDiseqcRxMessage")) {
-                mIsDiseqcRxMessage = isDiseqcRxMessage;
+                        TunerVersionChecker.TUNER_VERSION_1_1, "setCouldHandleDiseqcRxMessage")) {
+                mIsDiseqcRxMessage = couldReceiveDiseqcMessage;
             }
             return this;
         }
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
index 2147622..f8470b11 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -302,6 +302,7 @@
      *
      * @return the end frequency in Hz.
      */
+    @IntRange(from = 1)
     public int getEndFrequency() {
         return mEndFrequency;
     }
@@ -341,11 +342,15 @@
      *
      * @param endFrequency the end frequency used during blind scan. The default value is
      * {@link android.media.tv.tuner.Tuner#INVALID_FRONTEND_SETTING_FREQUENCY}.
+     * @throws IllegalArgumentException if the {@code endFrequency} is not greater than 0.
      */
     @IntRange(from = 1)
     public void setEndFrequency(int endFrequency) {
         if (TunerVersionChecker.checkHigherOrEqualVersionTo(
                 TunerVersionChecker.TUNER_VERSION_1_1, "setEndFrequency")) {
+            if (endFrequency < 1) {
+                throw new IllegalArgumentException("endFrequency must be greater than 0");
+            }
             mEndFrequency = endFrequency;
         }
     }
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
index 9bf7a5d..27627d7 100644
--- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -75,4 +75,7 @@
 
     /** Frontend scan message priority reported. */
     default void onPriorityReported(boolean isHighPriority) {}
+
+    /** DVBC Frontend Annex reported. */
+    default void onDvbcAnnexReported(@DvbcFrontendSettings.Annex int dvbcAnnex) {}
 }
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 724965d..c7fb50f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -31,6 +31,8 @@
     ],
 
     shared_libs: [
+        "audioclient-types-aidl-unstable-cpp",
+        "av-types-aidl-unstable-cpp",
         "libandroid_runtime",
         "libaudioclient",
         "libnativehelper",
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 27e1992..758f015 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -605,9 +605,13 @@
 
         jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].tsRecord().pts)
                 : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+        jlong firstMbInSlice = (eventsExt.size() > i)
+                ? static_cast<jint>(eventsExt[i].tsRecord().firstMbInSlice)
+                : static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
 
         jobject obj =
-                env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts);
+                env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber,
+                        pts, firstMbInSlice);
         env->SetObjectArrayElement(arr, i, obj);
     }
     return arr;
@@ -632,10 +636,13 @@
                 : static_cast<jint>(Constant::INVALID_MMTP_RECORD_EVENT_MPT_SEQUENCE_NUM);
         jlong pts = (eventsExt.size() > i) ? static_cast<jlong>(eventsExt[i].mmtpRecord().pts)
                 : static_cast<jlong>(Constant64Bit::INVALID_PRESENTATION_TIME_STAMP);
+        jlong firstMbInSlice = (eventsExt.size() > i)
+                ? static_cast<jint>(eventsExt[i].mmtpRecord().firstMbInSlice)
+                : static_cast<jint>(Constant::INVALID_FIRST_MACROBLOCK_IN_SLICE);
 
         jobject obj =
                 env->NewObject(eventClazz, eventInit, scHevcIndexMask, byteNumber,
-                        mpuSequenceNumber, pts);
+                        mpuSequenceNumber, pts, firstMbInSlice);
         env->SetObjectArrayElement(arr, i, obj);
     }
     return arr;
@@ -1058,10 +1065,18 @@
             bool isHighPriority = message.isHighPriority();
             env->CallVoidMethod(
                     mObject,
-                    env->GetMethodID(clazz, "onPriorityReported", "([B)V"),
+                    env->GetMethodID(clazz, "onPriorityReported", "(B)V"),
                     isHighPriority);
             break;
         }
+        case FrontendScanMessageTypeExt1_1::DVBC_ANNEX: {
+            jint dvbcAnnex = (jint) message.annex();
+            env->CallVoidMethod(
+                    mObject,
+                    env->GetMethodID(clazz, "onDvbcAnnexReported", "(I)V"),
+                    dvbcAnnex);
+            break;
+        }
         default:
             break;
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 3def945..ec1240f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -17,7 +17,6 @@
 package com.android.systemui;
 
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.car.navigationbar.CarNavigationBar;
 import com.android.systemui.car.notification.CarNotificationModule;
 import com.android.systemui.car.sideloaded.SideLoadedAppController;
@@ -48,8 +47,7 @@
 
 /** Binder for car specific {@link SystemUI} modules. */
 @Module(includes = {RecentsModule.class, StatusBarModule.class, NotificationsModule.class,
-        BubbleModule.class, KeyguardModule.class, OverlayWindowModule.class,
-        CarNotificationModule.class})
+        KeyguardModule.class, OverlayWindowModule.class, CarNotificationModule.class})
 public abstract class CarSystemUIBinder {
     /** Inject into AuthController. */
     @Binds
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 6a4c8c3..48421ce 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1385,4 +1385,9 @@
 
     <!-- Name of the 3.5mm and usb audio device. [CHAR LIMIT=50] -->
     <string name="media_transfer_wired_usb_device_name">Wired headphone</string>
+
+    <!-- Label for Wifi hotspot switch on. Toggles hotspot on [CHAR LIMIT=30] -->
+    <string name="wifi_hotspot_switch_on_text">On</string>
+    <!-- Label for Wifi hotspot switch off. Toggles hotspot off [CHAR LIMIT=30] -->
+    <string name="wifi_hotspot_switch_off_text">Off</string>
 </resources>
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f9268eec..39a6ed1 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -352,7 +352,7 @@
                 android:exported="true" />
 
         <activity
-            android:name=".bubbles.BubbleOverflowActivity"
+            android:name="com.android.wm.shell.bubbles.BubbleOverflowActivity"
             android:theme="@style/BubbleOverflow"
             android:excludeFromRecents="true"
             android:documentLaunchMode="always"
diff --git a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
deleted file mode 100644
index 8c7e82f..0000000
--- a/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-    Copyright (C) 2019 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.
--->
-<!--
-    The transparent circle outline that encircles the bubbles when they're in the dismiss target.
--->
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-    <stroke
-        android:width="1dp"
-        android:color="#66FFFFFF" />
-
-    <solid android:color="#B3000000" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/bubble_dismiss_icon.xml b/packages/SystemUI/res/drawable/bubble_dismiss_icon.xml
deleted file mode 100644
index 5c8de58..0000000
--- a/packages/SystemUI/res/drawable/bubble_dismiss_icon.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-    Copyright (C) 2019 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.
--->
-<!-- The 'X' bubble dismiss icon. This is just ic_close with a stroke. -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        android:width="24.0dp"
-        android:height="24.0dp"
-        android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
-    <path
-        android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
-        android:fillColor="#FFFFFFFF"
-        android:strokeColor="#FF000000"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_create_bubble.xml b/packages/SystemUI/res/drawable/ic_create_bubble.xml
deleted file mode 100644
index 4abbc81..0000000
--- a/packages/SystemUI/res/drawable/ic_create_bubble.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2019 The Android Open Source Project
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="20dp"
-    android:height="20dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-  <path
-      android:fillColor="#FF000000"
-      android:pathData="M23,5v8h-2V5H3v14h10v2v0H3c-1.1,0 -2,-0.9 -2,-2V5c0,-1.1 0.9,-2 2,-2h18C22.1,3 23,3.9 23,5zM10,8v2.59L5.71,6.29L4.29,7.71L8.59,12H6v2h6V8H10zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_screenshot_scroll.xml b/packages/SystemUI/res/drawable/ic_screenshot_scroll.xml
new file mode 100644
index 0000000..c260ba9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_screenshot_scroll.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+<!-- ic_unfold_more_24px.xml -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M12,5.83L15.17,9l1.41,-1.41L12,3 7.41,7.59 8.83,9 12,5.83zM12,18.17L8.83,15l-1.41,1.41L12,21l4.59,-4.59L15.17,15 12,18.17z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_stop_bubble.xml b/packages/SystemUI/res/drawable/ic_stop_bubble.xml
deleted file mode 100644
index 6cf67a7..0000000
--- a/packages/SystemUI/res/drawable/ic_stop_bubble.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="20dp"
-    android:height="20dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-  <path
-      android:fillColor="#FF000000"
-      android:pathData="M11.29,14.71L7,10.41V13H5V7h6v2H8.41l4.29,4.29L11.29,14.71zM21,3H3C1.9,3 1,3.9 1,5v14c0,1.1 0.9,2 2,2h10v0v-2H3V5h18v8h2V5C23,3.9 22.1,3 21,3zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/>
-</vector>
diff --git a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
index 040303a..71b414f 100644
--- a/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout-land/global_screenshot_preview.xml
@@ -28,6 +28,6 @@
     android:visibility="gone"
     android:background="@drawable/screenshot_rounded_corners"
     android:adjustViewBounds="true"
-    android:contentDescription="@string/screenshot_edit"
+    android:contentDescription="@string/screenshot_edit_label"
     app:layout_constraintBottom_toBottomOf="parent"
     app:layout_constraintStart_toStartOf="parent"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 1b5f9c1..6c20c1e 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -46,7 +46,7 @@
         android:layout_height="@dimen/screenshot_dismiss_button_tappable_size"
         android:elevation="7dp"
         android:visibility="gone"
-        android:contentDescription="@string/screenshot_dismiss_ui_description">
+        android:contentDescription="@string/screenshot_dismiss_description">
         <ImageView
             android:id="@+id/global_screenshot_dismiss_image"
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/global_screenshot_preview.xml b/packages/SystemUI/res/layout/global_screenshot_preview.xml
index c745854..5262407 100644
--- a/packages/SystemUI/res/layout/global_screenshot_preview.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_preview.xml
@@ -28,6 +28,6 @@
     android:visibility="gone"
     android:background="@drawable/screenshot_rounded_corners"
     android:adjustViewBounds="true"
-    android:contentDescription="@string/screenshot_edit"
+    android:contentDescription="@string/screenshot_edit_label"
     app:layout_constraintBottom_toBottomOf="parent"
     app:layout_constraintStart_toStartOf="parent"/>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index 26edf3a..096ec7d 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -56,6 +56,9 @@
                      android:id="@+id/screenshot_share_chip"/>
             <include layout="@layout/global_screenshot_action_chip"
                      android:id="@+id/screenshot_edit_chip"/>
+            <include layout="@layout/global_screenshot_action_chip"
+                     android:id="@+id/screenshot_scroll_chip"
+                     android:visibility="gone" />
         </LinearLayout>
     </HorizontalScrollView>
     <include layout="@layout/global_screenshot_preview"/>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index b584dfe..e45f4eb 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -36,10 +36,6 @@
     <dimen name="volume_tool_tip_right_margin">136dp</dimen>
     <dimen name="volume_tool_tip_top_margin">12dp</dimen>
 
-    <!-- Padding between status bar and bubbles when displayed in expanded state, smaller
-         value in landscape since we have limited vertical space-->
-    <dimen name="bubble_padding_top">4dp</dimen>
-
     <dimen name="controls_activity_view_top_offset">25dp</dimen>
 
     <dimen name="biometric_dialog_button_negative_max_width">140dp</dimen>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 6df8b4e..be36316 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -204,10 +204,6 @@
     <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color>
     <color name="global_screenshot_background_protection_start">#40000000</color> <!-- 25% black -->
 
-    <!-- Bubbles -->
-    <color name="bubbles_light">#FFFFFF</color>
-    <color name="bubbles_dark">@color/GM2_grey_800</color>
-
     <!-- GM2 colors -->
     <color name="GM2_grey_50">#F8F9FA</color>
     <color name="GM2_grey_100">#F1F3F4</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 17dc400..a2e9f39 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1159,90 +1159,6 @@
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">16dp</dimen>
 
-
-    <!-- How much each bubble is elevated. -->
-    <dimen name="bubble_elevation">1dp</dimen>
-    <!-- How much the bubble flyout text container is elevated. -->
-    <dimen name="bubble_flyout_elevation">4dp</dimen>
-    <!-- How much padding is around the left and right sides of the flyout text. -->
-    <dimen name="bubble_flyout_padding_x">12dp</dimen>
-    <!-- How much padding is around the top and bottom of the flyout text. -->
-    <dimen name="bubble_flyout_padding_y">10dp</dimen>
-    <!-- Size of the triangle that points from the flyout to the bubble stack. -->
-    <dimen name="bubble_flyout_pointer_size">6dp</dimen>
-    <!-- How much space to leave between the flyout (tip of the arrow) and the bubble stack. -->
-    <dimen name="bubble_flyout_space_from_bubble">8dp</dimen>
-    <!-- How much space to leave between the flyout text and the avatar displayed in the flyout. -->
-    <dimen name="bubble_flyout_avatar_message_space">6dp</dimen>
-    <!-- Padding between status bar and bubbles when displayed in expanded state -->
-    <dimen name="bubble_padding_top">16dp</dimen>
-    <!-- Size of individual bubbles. -->
-    <dimen name="individual_bubble_size">60dp</dimen>
-    <!-- Size of bubble bitmap. -->
-    <dimen name="bubble_bitmap_size">52dp</dimen>
-    <!-- Size of bubble icon bitmap. -->
-    <dimen name="bubble_overflow_icon_bitmap_size">24dp</dimen>
-    <!-- Extra padding added to the touchable rect for bubbles so they are easier to grab. -->
-    <dimen name="bubble_touch_padding">12dp</dimen>
-    <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
-    <dimen name="bubble_dismiss_encircle_size">52dp</dimen>
-    <!-- Padding around the view displayed when the bubble is expanded -->
-    <dimen name="bubble_expanded_view_padding">4dp</dimen>
-    <!-- This should be at least the size of bubble_expanded_view_padding; it is used to include
-         a slight touch slop around the expanded view. -->
-    <dimen name="bubble_expanded_view_slop">8dp</dimen>
-    <!-- Default (and minimum) height of the expanded view shown when the bubble is expanded -->
-    <dimen name="bubble_expanded_default_height">180dp</dimen>
-    <!-- Default height of bubble overflow -->
-    <dimen name="bubble_overflow_height">480dp</dimen>
-    <!-- Bubble overflow padding when there are no bubbles  -->
-    <dimen name="bubble_overflow_empty_state_padding">16dp</dimen>
-    <!-- Padding of container for overflow bubbles -->
-    <dimen name="bubble_overflow_padding">15dp</dimen>
-    <!-- Padding of label for bubble overflow view -->
-    <dimen name="bubble_overflow_text_padding">7dp</dimen>
-    <!-- Height of bubble overflow empty state illustration -->
-    <dimen name="bubble_empty_overflow_image_height">200dp</dimen>
-    <!-- Padding of bubble overflow empty state subtitle -->
-    <dimen name="bubble_empty_overflow_subtitle_padding">50dp</dimen>
-    <!-- Height of the triangle that points to the expanded bubble -->
-    <dimen name="bubble_pointer_height">8dp</dimen>
-    <!-- Width of the triangle that points to the expanded bubble -->
-    <dimen name="bubble_pointer_width">12dp</dimen>
-    <!-- Extra padding around the dismiss target for bubbles -->
-    <dimen name="bubble_dismiss_slop">16dp</dimen>
-    <!-- Height of button allowing users to adjust settings for bubbles. -->
-    <dimen name="bubble_manage_button_height">48dp</dimen>
-    <!-- Max width of the message bubble-->
-    <dimen name="bubble_message_max_width">144dp</dimen>
-    <!-- Min width of the message bubble -->
-    <dimen name="bubble_message_min_width">32dp</dimen>
-    <!-- Interior padding of the message bubble -->
-    <dimen name="bubble_message_padding">4dp</dimen>
-    <!-- Offset between bubbles in their stacked position. -->
-    <dimen name="bubble_stack_offset">10dp</dimen>
-    <!-- Offset between stack y and animation y for bubble swap. -->
-    <dimen name="bubble_swap_animation_offset">15dp</dimen>
-    <!-- How far offscreen the bubble stack rests. Cuts off padding and part of icon bitmap. -->
-    <dimen name="bubble_stack_offscreen">9dp</dimen>
-    <!-- How far down the screen the stack starts. -->
-    <dimen name="bubble_stack_starting_offset_y">120dp</dimen>
-    <!-- Space between the pointer triangle and the bubble expanded view -->
-    <dimen name="bubble_pointer_margin">8dp</dimen>
-    <!-- Padding applied to the bubble dismiss target. Touches in this padding cause the bubbles to
-         snap to the dismiss target. -->
-    <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
-    <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
-    <dimen name="bubble_manage_menu_elevation">4dp</dimen>
-
-    <!-- Bubbles user education views -->
-    <dimen name="bubbles_manage_education_width">160dp</dimen>
-    <!-- The inset from the top bound of the manage button to place the user education. -->
-    <dimen name="bubbles_manage_education_top_inset">65dp</dimen>
-    <!-- Size of padding for the user education cling, this should at minimum be larger than
-        individual_bubble_size + some padding. -->
-    <dimen name="bubble_stack_user_education_side_inset">72dp</dimen>
-
     <!-- Size of the RAT type for CellularTile -->
     <dimen name="celltile_rat_type_size">10sp</dimen>
 
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 7d3135a..5f68bdb4 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -130,12 +130,6 @@
     <item type="id" name="action_snooze_assistant_suggestion_1"/>
     <item type="id" name="action_snooze"/>
 
-    <!-- Accessibility actions for bubbles. -->
-    <item type="id" name="action_move_top_left"/>
-    <item type="id" name="action_move_top_right"/>
-    <item type="id" name="action_move_bottom_left"/>
-    <item type="id" name="action_move_bottom_right"/>
-
     <!-- For StatusIconContainer to tag its icon views -->
     <item type="id" name="status_bar_view_state_tag" />
 
@@ -146,17 +140,6 @@
     <!-- Optional cancel button on Keyguard -->
     <item type="id" name="cancel_button"/>
 
-    <!-- For saving PhysicsAnimationLayout animations/animators as view tags. -->
-    <item type="id" name="translation_x_dynamicanimation_tag"/>
-    <item type="id" name="translation_y_dynamicanimation_tag"/>
-    <item type="id" name="translation_z_dynamicanimation_tag"/>
-    <item type="id" name="alpha_dynamicanimation_tag"/>
-    <item type="id" name="scale_x_dynamicanimation_tag"/>
-    <item type="id" name="scale_y_dynamicanimation_tag"/>
-    <item type="id" name="physics_animator_tag"/>
-    <item type="id" name="target_animator_tag" />
-    <item type="id" name="reorder_animator_tag"/>
-
     <!-- Global Actions Menu -->
     <item type="id" name="global_actions_view" />
 
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index b1e91c8..b50b5c1 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -22,17 +22,6 @@
     <integer name="qs_footer_actions_width">0</integer>
     <integer name="qs_footer_actions_weight">1</integer>
 
-    <!-- Maximum number of bubbles to render and animate at one time. While the animations used are
-         lightweight translation animations, this number can be reduced on lower end devices if any
-         performance issues arise. -->
-    <integer name="bubbles_max_rendered">5</integer>
-
-    <!-- Number of columns in bubble overflow. -->
-    <integer name="bubbles_overflow_columns">4</integer>
-
-    <!-- Maximum number of bubbles we allow in overflow before we dismiss the oldest one. -->
-    <integer name="bubbles_max_overflow">16</integer>
-
     <integer name="magnification_default_scale">2</integer>
 
     <!-- The position of the volume dialog on the screen.
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d5c9823..773ef7d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -233,10 +233,16 @@
     <!-- Notification text displayed when we fail to take a screenshot. [CHAR LIMIT=100] -->
     <string name="screenshot_failed_to_capture_text">Taking screenshots isn\'t allowed by the app or
         your organization</string>
+    <!-- Label for UI element which allows editing the screenshot [CHAR LIMIT=30] -->
+    <string name="screenshot_edit_label">Edit</string>
     <!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] -->
-    <string name="screenshot_edit">Edit screenshot</string>
+    <string name="screenshot_edit_description">Edit screenshot</string>
+    <!-- Label for UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=30] -->
+    <string name="screenshot_scroll_label">Scroll</string>
+    <!-- Content description UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=NONE] -->
+    <string name="screenshot_scroll_description">Scroll screenshot</string>
     <!-- Content description indicating that tapping a button will dismiss the screenshots UI [CHAR LIMIT=NONE] -->
-    <string name="screenshot_dismiss_ui_description">Dismiss screenshot</string>
+    <string name="screenshot_dismiss_description">Dismiss screenshot</string>
     <!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] -->
     <string name="screenshot_preview_description">Screenshot preview</string>
 
@@ -646,9 +652,6 @@
     <!-- Content description to tell the user a notification has been removed from the notification shade -->
     <string name="accessibility_notification_dismissed">Notification dismissed.</string>
 
-    <!-- Content description to tell the user a bubble has been dismissed. -->
-    <string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
-
     <!-- Content description for the notification shade panel (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_desc_notification_shade">Notification shade.</string>
     <!-- Content description for the quick settings panel (not shown on the screen). [CHAR LIMIT=NONE] -->
@@ -1846,9 +1849,6 @@
     <string name="notification_alert_title">Default</string>
 
     <!-- [CHAR LIMIT=100] Notification Importance title -->
-    <string name="notification_bubble_title">Bubble</string>
-
-    <!-- [CHAR LIMIT=100] Notification Importance title -->
     <string name="notification_automatic_title">Automatic</string>
 
     <!-- [CHAR LIMIT=150] Notification Importance title: low importance level summary -->
@@ -1881,12 +1881,6 @@
     <!-- Text shown in notification guts for conversation notifications that don't implement the full feature -->
     <string name="no_shortcut"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> doesn\u2019t support conversation features</string>
 
-    <!-- [CHAR LIMIT=NONE] Empty overflow title -->
-    <string name="bubble_overflow_empty_title">No recent bubbles</string>
-
-    <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
-    <string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
-
     <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
     <string name="notification_unblockable_desc">These notifications can\'t be modified.</string>
 
@@ -2607,45 +2601,8 @@
     <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
     <string name="restart_button_description">Tap to restart this app and go full screen.</string>
 
-    <!-- Text used for content description of settings button in the header of expanded bubble
-         view. [CHAR_LIMIT=NONE] -->
-    <string name="bubbles_settings_button_description">Settings for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g> bubbles</string>
-    <!-- Content description for button that shows bubble overflow on click [CHAR LIMIT=NONE] -->
-    <string name="bubble_overflow_button_content_description">Overflow</string>
-    <!-- Action to add overflow bubble back to stack. [CHAR LIMIT=NONE] -->
-    <string name="bubble_accessibility_action_add_back">Add back to stack</string>
-    <!-- The text for the manage bubbles link. [CHAR LIMIT=NONE] -->
-    <string name="manage_bubbles_text">Manage</string>
-    <!-- Content description when a bubble is focused. [CHAR LIMIT=NONE] -->
-    <string name="bubble_content_description_single"><xliff:g id="notification_title" example="some title">%1$s</xliff:g> from <xliff:g id="app_name" example="YouTube">%2$s</xliff:g></string>
-    <!-- Content description when the stack of bubbles is focused. [CHAR LIMIT=NONE] -->
-    <string name="bubble_content_description_stack"><xliff:g id="notification_title" example="some title">%1$s</xliff:g> from <xliff:g id="app_name" example="YouTube">%2$s</xliff:g> and <xliff:g id="bubble_count" example="4">%3$d</xliff:g> more</string>
     <!-- Action in accessibility menu to move the stack of bubbles [CHAR LIMIT=20] -->
     <string name="bubble_accessibility_action_move">Move</string>
-    <!-- Action in accessibility menu to move the stack of bubbles to the top left of the screen. [CHAR LIMIT=30] -->
-    <string name="bubble_accessibility_action_move_top_left">Move top left</string>
-    <!-- Action in accessibility menu to move the stack of bubbles to the top right of the screen. [CHAR LIMIT=30] -->
-    <string name="bubble_accessibility_action_move_top_right">Move top right</string>
-    <!-- Action in accessibility menu to move the stack of bubbles to the bottom left of the screen. [CHAR LIMIT=30]-->
-    <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
-    <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
-    <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
-    <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] -->
-    <string name="bubble_dismiss_text">Dismiss bubble</string>
-    <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]-->
-    <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string>
-    <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
-    <string name="bubbles_user_education_title">Chat using bubbles</string>
-    <!-- Descriptive text for the bubble feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
-    <string name="bubbles_user_education_description">New conversations appear as floating icons, or bubbles. Tap to open bubble. Drag to move it.</string>
-    <!-- Title text for the bubble "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
-    <string name="bubbles_user_education_manage_title">Control bubbles anytime</string>
-    <!-- Descriptive text for the bubble "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
-    <string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
-    <!-- Button text for dismissing the bubble "manage" button tool tip  [CHAR LIMIT=20]-->
-    <string name="bubbles_user_education_got_it">Got it</string>
-    <!-- Label for the button that takes the user to the notification settings for the given app. -->
-    <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
 
     <!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] -->
     <string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 628193d..5b89f7f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -22,6 +22,7 @@
 import android.util.TypedValue;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.keyguard.clock.ClockManager;
@@ -30,6 +31,9 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.NotificationIconContainer;
 import com.android.systemui.util.ViewController;
@@ -51,6 +55,7 @@
     private final ClockManager mClockManager;
     private final KeyguardSliceViewController mKeyguardSliceViewController;
     private final NotificationIconAreaController mNotificationIconAreaController;
+    private FrameLayout mNewLockscreenClockFrame;
 
     private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
 
@@ -114,6 +119,7 @@
         mColorExtractor.addOnColorsChangedListener(mColorsListener);
         mView.updateColors(getGradientColors());
         updateAodIcons();
+        mNewLockscreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view);
     }
 
     @Override
@@ -180,6 +186,21 @@
     }
 
     /**
+     * Update position of the view, with optional animation. Move the slice view and the clock
+     * slightly towards the center in order to prevent burn-in. Y positioning occurs at the
+     * view parent level.
+     */
+    void updatePosition(int x, AnimationProperties props, boolean animate) {
+        x = Math.abs(x);
+        if (mNewLockscreenClockFrame != null) {
+            PropertyAnimator.setProperty(mNewLockscreenClockFrame, AnimatableProperty.TRANSLATION_X,
+                    -x, props, animate);
+        }
+        mKeyguardSliceViewController.updatePosition(x, props, animate);
+        mNotificationIconAreaController.updatePosition(x, props, animate);
+    }
+
+    /**
      * Update lockscreen mode that may change clock display.
      */
     void updateLockScreenMode(int mode) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 8b55b06..02b18b2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -42,6 +42,9 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.ViewController;
@@ -199,6 +202,13 @@
         Trace.endSection();
     }
 
+    /**
+     * Update position of the view, with optional animation
+     */
+    void updatePosition(int x, AnimationProperties props, boolean animate) {
+        PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, x, props, animate);
+    }
+
     void showSlice(Slice slice) {
         Trace.beginSection("KeyguardSliceViewController#showSlice");
         if (slice == null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index cc0d1b6..cc7b832 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -186,12 +186,23 @@
     /**
      * Update position of the view with an optional animation
      */
-    public void updatePosition(int clockTranslationX, int clockTranslationY,
-            boolean animateClock) {
-        PropertyAnimator.setProperty(mView, AnimatableProperty.X,
-                clockTranslationX, CLOCK_ANIMATION_PROPERTIES, animateClock);
-        PropertyAnimator.setProperty(mView, AnimatableProperty.Y,
-                clockTranslationY, CLOCK_ANIMATION_PROPERTIES, animateClock);
+    public void updatePosition(int x, int y, boolean animate) {
+        PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
+                animate);
+
+        if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+            // reset any prior movement
+            PropertyAnimator.setProperty(mView, AnimatableProperty.X, 0,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+
+            mKeyguardClockSwitchController.updatePosition(x, CLOCK_ANIMATION_PROPERTIES, animate);
+        } else {
+            // reset any prior movement
+            mKeyguardClockSwitchController.updatePosition(0, CLOCK_ANIMATION_PROPERTIES, animate);
+
+            PropertyAnimator.setProperty(mView, AnimatableProperty.X, x,
+                    CLOCK_ANIMATION_PROPERTIES, animate);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0e6bc24..31b0701 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -289,6 +289,8 @@
     private final DevicePolicyManager mDevicePolicyManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
     private boolean mLogoutEnabled;
+    // cached value to avoid IPCs
+    private boolean mIsUdfpsEnrolled;
     // If the user long pressed the lock icon, disabling face auth for the current session.
     private boolean mLockIconPressed;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1857,7 +1859,15 @@
 
     private void updateLockScreenMode() {
         mLockScreenMode = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.SHOW_NEW_LOCKSCREEN, mAuthController.isUdfpsEnrolled() ? 1 : 0);
+                Settings.Global.SHOW_NEW_LOCKSCREEN,
+                isUdfpsEnrolled() ? 1 : 0);
+    }
+
+    private void updateUdfpsEnrolled(int userId) {
+        mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
+    }
+    public boolean isUdfpsEnrolled() {
+        return mIsUdfpsEnrolled;
     }
 
     private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
@@ -2098,6 +2108,7 @@
         }
         if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
         int userId = getCurrentUser();
+        updateUdfpsEnrolled(userId);
         if (isUnlockWithFingerprintPossible(userId)) {
             if (mFingerprintCancelSignal != null) {
                 mFingerprintCancelSignal.cancel();
@@ -3069,6 +3080,7 @@
                     + " expected=" + (shouldListenForFingerprint() ? 1 : 0));
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
+            pw.println("    udfpsEnrolled=" + isUdfpsEnrolled());
         }
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
             final int userId = ActivityManager.getCurrentUser();
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index b30103e..17bb40e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -108,12 +108,14 @@
                     .setPip(mWMComponent.getPip())
                     .setSplitScreen(mWMComponent.getSplitScreen())
                     .setOneHanded(mWMComponent.getOneHanded())
+                    .setBubbles(mWMComponent.getBubbles())
                     .setShellDump(mWMComponent.getShellDump());
         } else {
             // TODO: Call on prepareSysUIComponentBuilder but not with real components.
             builder = builder.setPip(Optional.ofNullable(null))
                     .setSplitScreen(Optional.ofNullable(null))
                     .setOneHanded(Optional.ofNullable(null))
+                    .setBubbles(Optional.ofNullable(null))
                     .setShellDump(Optional.ofNullable(null));
         }
         mSysUIComponent = builder
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index c72bc25..a6b1b90 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.inject.Inject;
@@ -81,6 +82,7 @@
 
     @Nullable private final List<FingerprintSensorPropertiesInternal> mFpProps;
     @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
+    @Nullable private final List<FingerprintSensorPropertiesInternal> mUdfpsProps;
 
     // TODO: These should just be saved from onSaveState
     private SomeArgs mCurrentDialogArgs;
@@ -314,6 +316,16 @@
                 : null;
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
 
+        List<FingerprintSensorPropertiesInternal> udfpsProps = new ArrayList<>();
+        if (mFpProps != null) {
+            for (FingerprintSensorPropertiesInternal props : mFpProps) {
+                if (props.isAnyUdfpsType()) {
+                    udfpsProps.add(props);
+                }
+            }
+        }
+        mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null;
+
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
 
@@ -326,15 +338,9 @@
         mCommandQueue.addCallback(this);
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
-        if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
-            final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
-                    mFingerprintManager.getSensorPropertiesInternal();
-            for (FingerprintSensorPropertiesInternal props : fingerprintSensorProperties) {
-                if (props.isAnyUdfpsType()) {
-                    mUdfpsController = mUdfpsControllerFactory.get();
-                    break;
-                }
-            }
+        if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()
+                && mUdfpsProps != null) {
+            mUdfpsController = mUdfpsControllerFactory.get();
         }
 
         try {
@@ -484,12 +490,14 @@
     }
 
    /**
-     * Whether the current user has a UDFP enrolled.
+     * Whether the passed userId has enrolled UDFPS.
      */
-    public boolean isUdfpsEnrolled() {
-        // TODO: (b/171392825) right now only checks whether the UDFPS sensor exists on this device
-        //  but not whether user has enrolled or not
-        return mUdfpsController != null;
+    public boolean isUdfpsEnrolled(int userId) {
+        if (mUdfpsController == null) {
+            return false;
+        }
+
+        return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
     }
 
     private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
deleted file mode 100644
index ffb650d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2019 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.bubbles;
-
-import static android.app.Notification.EXTRA_MESSAGES;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Color;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Common class for experiments controlled via secure settings.
- */
-public class BubbleExperimentConfig {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
-
-    private static final int BUBBLE_HEIGHT = 10000;
-
-    private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble";
-    private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false;
-
-    private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble";
-    private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false;
-
-    private static final String ALLOW_SHORTCUTS_TO_BUBBLE = "allow_shortcuts_to_bubble";
-    private static final boolean ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT = false;
-
-    private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
-
-    /**
-     * When true, if a notification has the information necessary to bubble (i.e. valid
-     * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
-     * object will be created by the system and added to the notification.
-     * <p>
-     * This does not produce a bubble, only adds the metadata based on the notification info.
-     */
-    static boolean allowAnyNotifToBubble(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ALLOW_ANY_NOTIF_TO_BUBBLE,
-                ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
-    }
-
-    /**
-     * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
-     * are using {@link Notification.MessagingStyle} and have remote input.
-     */
-    static boolean allowMessageNotifsToBubble(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ALLOW_MESSAGE_NOTIFS_TO_BUBBLE,
-                ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
-    }
-
-    /**
-     * When true, if the notification is able to bubble via {@link #allowAnyNotifToBubble(Context)}
-     * or {@link #allowMessageNotifsToBubble(Context)} or via normal BubbleMetadata, then a new
-     * BubbleMetadata object is constructed based on the shortcut info.
-     * <p>
-     * This does not produce a bubble, only adds the metadata based on shortcut info.
-     */
-    static boolean useShortcutInfoToBubble(Context context) {
-        return Settings.Secure.getInt(context.getContentResolver(),
-                ALLOW_SHORTCUTS_TO_BUBBLE,
-                ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
-    }
-
-    /**
-     * Returns whether the provided package is whitelisted to bubble.
-     */
-    static boolean isPackageWhitelistedToAutoBubble(Context context, String packageName) {
-        String unsplitList = Settings.Secure.getString(context.getContentResolver(),
-                WHITELISTED_AUTO_BUBBLE_APPS);
-        if (unsplitList != null) {
-            // We expect the list to be separated by commas and no white space (but we trim in case)
-            String[] packageList = unsplitList.split(",");
-            for (int i = 0; i < packageList.length; i++) {
-                if (packageList[i].trim().equals(packageName)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
-     * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
-     * the notification has necessary info for BubbleMetadata.
-     *
-     * @return whether an adjustment was made.
-     */
-    static boolean adjustForExperiments(Context context, NotificationEntry entry,
-            boolean previouslyUserCreated, boolean userBlocked) {
-        Notification.BubbleMetadata metadata = null;
-        boolean addedMetadata = false;
-        boolean whiteListedToAutoBubble =
-                isPackageWhitelistedToAutoBubble(context, entry.getSbn().getPackageName());
-
-        Notification notification = entry.getSbn().getNotification();
-        boolean isMessage = Notification.MessagingStyle.class.equals(
-                notification.getNotificationStyle());
-        boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
-                || allowAnyNotifToBubble(context);
-
-        boolean useShortcutInfo = useShortcutInfoToBubble(context);
-        String shortcutId = entry.getSbn().getNotification().getShortcutId();
-
-        boolean hasMetadata = entry.getBubbleMetadata() != null;
-        if ((!hasMetadata && (previouslyUserCreated || bubbleNotifForExperiment))
-                || useShortcutInfo) {
-            if (DEBUG_EXPERIMENTS) {
-                Log.d(TAG, "Adjusting " + entry.getKey() + " for bubble experiment."
-                        + " allowMessages=" + allowMessageNotifsToBubble(context)
-                        + " isMessage=" + isMessage
-                        + " allowNotifs=" + allowAnyNotifToBubble(context)
-                        + " useShortcutInfo=" + useShortcutInfo
-                        + " previouslyUserCreated=" + previouslyUserCreated);
-            }
-        }
-
-        if (useShortcutInfo && shortcutId != null) {
-            // We don't actually get anything useful from ShortcutInfo so just check existence
-            ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
-                    entry.getSbn().getUser(), shortcutId);
-            if (info != null) {
-                metadata = createForShortcut(shortcutId);
-            }
-
-            // Replace existing metadata with shortcut, or we're bubbling for experiment
-            boolean shouldBubble = entry.getBubbleMetadata() != null
-                    || bubbleNotifForExperiment
-                    || previouslyUserCreated;
-            if (shouldBubble && metadata != null) {
-                if (DEBUG_EXPERIMENTS) {
-                    Log.d(TAG, "Adding experimental shortcut bubble for: " + entry.getKey());
-                }
-                entry.setBubbleMetadata(metadata);
-                addedMetadata = true;
-            }
-        }
-
-        // Didn't get metadata from a shortcut & we're bubbling for experiment
-        if (entry.getBubbleMetadata() == null
-                && (bubbleNotifForExperiment || previouslyUserCreated)) {
-            metadata = createFromNotif(context, entry);
-            if (metadata != null) {
-                if (DEBUG_EXPERIMENTS) {
-                    Log.d(TAG, "Adding experimental notification bubble for: " + entry.getKey());
-                }
-                entry.setBubbleMetadata(metadata);
-                addedMetadata = true;
-            }
-        }
-
-        boolean bubbleForWhitelist = !userBlocked
-                && whiteListedToAutoBubble
-                && (addedMetadata || hasMetadata);
-        if ((previouslyUserCreated && addedMetadata) || bubbleForWhitelist) {
-            // Update to a previous bubble (or new autobubble), set its flag now.
-            if (DEBUG_EXPERIMENTS) {
-                Log.d(TAG, "Setting FLAG_BUBBLE for: " + entry.getKey());
-            }
-            entry.setFlagBubble(true);
-            return true;
-        }
-        return addedMetadata;
-    }
-
-    static Notification.BubbleMetadata createFromNotif(Context context, NotificationEntry entry) {
-        Notification notification = entry.getSbn().getNotification();
-        final PendingIntent intent = notification.contentIntent;
-        Icon icon = null;
-        // Use the icon of the person if available
-        List<Person> personList = getPeopleFromNotification(entry);
-        if (personList.size() > 0) {
-            final Person person = personList.get(0);
-            if (person != null) {
-                icon = person.getIcon();
-                if (icon == null) {
-                    // Lets try and grab the icon constructed by the layout
-                    Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
-                    if (d instanceof  BitmapDrawable) {
-                        icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
-                    }
-                }
-            }
-        }
-        if (icon == null) {
-            boolean shouldTint = notification.getLargeIcon() == null;
-            icon = shouldTint
-                    ? notification.getSmallIcon()
-                    : notification.getLargeIcon();
-            if (shouldTint) {
-                int notifColor = entry.getSbn().getNotification().color;
-                notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
-                notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
-                        true /* findFg */, 3f);
-                icon.setTint(notifColor);
-            }
-        }
-        if (intent != null) {
-            return new Notification.BubbleMetadata.Builder(intent, icon)
-                    .setDesiredHeight(BUBBLE_HEIGHT)
-                    .build();
-        }
-        return null;
-    }
-
-    static Notification.BubbleMetadata createForShortcut(String shortcutId) {
-        return new Notification.BubbleMetadata.Builder(shortcutId)
-                .setDesiredHeight(BUBBLE_HEIGHT)
-                .build();
-    }
-
-    static ShortcutInfo getShortcutInfo(Context context, String packageName, UserHandle user,
-            String shortcutId) {
-        LauncherApps launcherAppService =
-                (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
-        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
-        if (packageName != null) {
-            query.setPackage(packageName);
-        }
-        if (shortcutId != null) {
-            query.setShortcutIds(Arrays.asList(shortcutId));
-        }
-        query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST);
-        List<ShortcutInfo> shortcuts = launcherAppService.getShortcuts(query, user);
-        return shortcuts != null && shortcuts.size() > 0
-                ? shortcuts.get(0)
-                : null;
-    }
-
-    static List<Person> getPeopleFromNotification(NotificationEntry entry) {
-        Bundle extras = entry.getSbn().getNotification().extras;
-        ArrayList<Person> personList = new ArrayList<>();
-        if (extras == null) {
-            return personList;
-        }
-
-        List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
-
-        if (p != null) {
-            personList.addAll(p);
-        }
-
-        if (Notification.MessagingStyle.class.equals(
-                entry.getSbn().getNotification().getNotificationStyle())) {
-            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
-            if (!ArrayUtils.isEmpty(messages)) {
-                for (Notification.MessagingStyle.Message message :
-                        Notification.MessagingStyle.Message
-                                .getMessagesFromBundleArray(messages)) {
-                    personList.add(message.getSenderPerson());
-                }
-            }
-        }
-        return personList;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
deleted file mode 100644
index 5a7e033..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles.dagger;
-
-import android.app.INotificationManager;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.os.Handler;
-import android.view.WindowManager;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.Bubbles;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.wmshell.BubblesManager;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-
-import java.util.Optional;
-
-import dagger.Module;
-import dagger.Provides;
-
-/** */
-@Module
-public interface BubbleModule {
-
-    /**
-     */
-    @SysUISingleton
-    @Provides
-    static Bubbles newBubbleController(Context context,
-            FloatingContentCoordinator floatingContentCoordinator,
-            IStatusBarService statusBarService,
-            WindowManager windowManager,
-            WindowManagerShellWrapper windowManagerShellWrapper,
-            LauncherApps launcherApps,
-            UiEventLogger uiEventLogger,
-            @Main Handler mainHandler,
-            ShellTaskOrganizer organizer) {
-        return BubbleController.create(context, null /* synchronizer */, floatingContentCoordinator,
-                statusBarService, windowManager, windowManagerShellWrapper, launcherApps,
-                uiEventLogger, mainHandler, organizer);
-    }
-
-    /** Provides Optional of BubbleManager */
-    @SysUISingleton
-    @Provides
-    static Optional<BubblesManager> provideBubblesManager(Context context,
-            Optional<Bubbles> bubblesOptional,
-            NotificationShadeWindowController notificationShadeWindowController,
-            StatusBarStateController statusBarStateController, ShadeController shadeController,
-            ConfigurationController configurationController,
-            @Nullable IStatusBarService statusBarService, INotificationManager notificationManager,
-            NotificationInterruptStateProvider interruptionStateProvider,
-            ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager,
-            NotificationGroupManagerLegacy groupManager, NotificationEntryManager entryManager,
-            NotifPipeline notifPipeline, SysUiState sysUiState, FeatureFlags featureFlags,
-            DumpManager dumpManager) {
-        return Optional.ofNullable(BubblesManager.create(context, bubblesOptional,
-                notificationShadeWindowController, statusBarStateController, shadeController,
-                configurationController, statusBarService, notificationManager,
-                interruptionStateProvider, zenModeController, notifUserManager,
-                groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager));
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index 554d9cb..53383d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -22,9 +22,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
-import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.animation.FlingAnimationUtils;
-import com.android.wm.shell.common.FloatingContentCoordinator;
 
 import javax.inject.Singleton;
 
@@ -51,22 +49,6 @@
         GlobalConcurrencyModule.class})
 public class GlobalModule {
 
-    // TODO(b/161980186): Currently only used by Bubbles, can move back to WMShellBaseModule once
-    //                    Bubbles has migrated over
-    @Singleton
-    @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
-    }
-
-    // TODO(b/161980186): Currently only used by Bubbles, can move back to WMShellBaseModule once
-    //                    Bubbles has migrated over
-    @Singleton
-    @Provides
-    static WindowManagerShellWrapper provideWindowManagerShellWrapper() {
-        return new WindowManagerShellWrapper();
-    }
-
     // TODO(b/162923491): This should not be a singleton at all, the display metrics can change and
     //                    callers should be creating a new builder on demand
     @Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index d73633e..b94a68b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -27,6 +27,7 @@
 import com.android.systemui.util.InjectionInflationController;
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -64,6 +65,9 @@
         Builder setOneHanded(Optional<OneHanded> o);
 
         @BindsInstance
+        Builder setBubbles(Optional<Bubbles> b);
+
+        @BindsInstance
         Builder setInputConsumerController(InputConsumerController i);
 
         @BindsInstance
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 1f6288a..c0013d8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -24,7 +24,6 @@
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.accessibility.WindowMagnification;
 import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -51,8 +50,7 @@
 /**
  * SystemUI objects that are injectable should go here.
  */
-@Module(includes = {RecentsModule.class, StatusBarModule.class, BubbleModule.class,
-        KeyguardModule.class})
+@Module(includes = {RecentsModule.class, StatusBarModule.class, KeyguardModule.class})
 public abstract class SystemUIBinder {
     /** Inject into AuthController. */
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a982ec5..780bb5b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -16,32 +16,49 @@
 
 package com.android.systemui.dagger;
 
+import android.app.INotificationManager;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.BootCompleteCache;
 import com.android.systemui.BootCompleteCacheImpl;
 import com.android.systemui.appops.dagger.AppOpsModule;
 import com.android.systemui.assist.AssistModule;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.controls.dagger.ControlsModule;
 import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.doze.dagger.DozeComponent;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.fragments.FragmentService;
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.dagger.PowerModule;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.settings.dagger.SettingsModule;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
+import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
 import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
 import com.android.systemui.tuner.dagger.TunerModule;
@@ -53,6 +70,10 @@
 import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.util.time.SystemClockImpl;
 import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
 
 import dagger.Binds;
 import dagger.BindsOptionalOf;
@@ -126,10 +147,28 @@
     @BindsOptionalOf
     abstract StatusBar optionalStatusBar();
 
-    @BindsOptionalOf
-    abstract Bubbles optionalBubbles();
-
     @SysUISingleton
     @Binds
     abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
+
+    /** Provides Optional of BubbleManager */
+    @SysUISingleton
+    @Provides
+    static Optional<BubblesManager> provideBubblesManager(Context context,
+            Optional<Bubbles> bubblesOptional,
+            NotificationShadeWindowController notificationShadeWindowController,
+            StatusBarStateController statusBarStateController, ShadeController shadeController,
+            ConfigurationController configurationController,
+            @Nullable IStatusBarService statusBarService, INotificationManager notificationManager,
+            NotificationInterruptStateProvider interruptionStateProvider,
+            ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager,
+            NotificationGroupManagerLegacy groupManager, NotificationEntryManager entryManager,
+            NotifPipeline notifPipeline, SysUiState sysUiState, FeatureFlags featureFlags,
+            DumpManager dumpManager) {
+        return Optional.ofNullable(BubblesManager.create(context, bubblesOptional,
+                notificationShadeWindowController, statusBarStateController, shadeController,
+                configurationController, statusBarService, notificationManager,
+                interruptionStateProvider, zenModeController, notifUserManager,
+                groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager));
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index 6635286..8f3d8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -21,6 +21,7 @@
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.ShellInit;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.splitscreen.SplitScreen;
@@ -64,13 +65,11 @@
     InputConsumerController getInputConsumerController();
 
     // TODO(b/162923491): To be removed once Bubbles migrates over to the Shell
-
     @WMSingleton
     ShellTaskOrganizer getShellTaskOrganizer();
 
     // TODO(b/162923491): We currently pass the instances through to SysUI, but that may change
     //                    depending on the threading mechanism we go with
-
     @WMSingleton
     Optional<OneHanded> getOneHanded();
 
@@ -79,4 +78,7 @@
 
     @WMSingleton
     Optional<SplitScreen> getSplitScreen();
+
+    @WMSingleton
+    Optional<Bubbles> getBubbles();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index f07e5af..ebfce66 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -44,6 +44,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.plugins.SensorManagerPlugin;
 import com.android.systemui.statusbar.phone.DozeParameters;
@@ -156,7 +157,7 @@
                         findSensorWithType(config.udfpsLongPressSensorType()),
                         "doze_pulse_on_auth",
                         true /* settingDef */,
-                        authController.isUdfpsEnrolled() /* configured */,
+                        authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()),
                         DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS,
                         true /* reports touch coordinates */,
                         true /* touchscreen */,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index cfcceb2..619729e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -57,7 +57,7 @@
     protected TextView mDetailDoneButton;
     private QSDetailClipper mClipper;
     private DetailAdapter mDetailAdapter;
-    private QSPanel mQsPanel;
+    private QSPanelController mQsPanelController;
 
     protected View mQsDetailHeader;
     protected TextView mQsDetailHeaderTitle;
@@ -114,19 +114,20 @@
             public void onClick(View v) {
                 announceForAccessibility(
                         mContext.getString(R.string.accessibility_desc_quick_settings));
-                mQsPanel.closeDetail();
+                mQsPanelController.closeDetail();
             }
         };
         mDetailDoneButton.setOnClickListener(doneListener);
     }
 
     /** */
-    public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, QSFooter footer) {
-        mQsPanel = panel;
+    public void setQsPanel(QSPanelController panelController, QuickStatusBarHeader header,
+            QSFooter footer) {
+        mQsPanelController = panelController;
         mHeader = header;
         mFooter = footer;
         mHeader.setCallback(mQsPanelCallback);
-        mQsPanel.setCallback(mQsPanelCallback);
+        mQsPanelController.setCallback(mQsPanelCallback);
     }
 
     public void setHost(QSTileHost host) {
@@ -221,7 +222,7 @@
             listener = mTeardownDetailWhenDone;
             mHeader.setVisibility(View.VISIBLE);
             mFooter.setVisibility(View.VISIBLE);
-            mQsPanel.setGridContentVisibility(true);
+            mQsPanelController.setGridContentVisibility(true);
             mQsPanelCallback.onScanStateChanged(false);
         }
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -362,7 +363,7 @@
         public void onAnimationEnd(Animator animation) {
             // Only hide content if still in detail state.
             if (mDetailAdapter != null) {
-                mQsPanel.setGridContentVisibility(false);
+                mQsPanelController.setGridContentVisibility(false);
                 mHeader.setVisibility(View.INVISIBLE);
                 mFooter.setVisibility(View.INVISIBLE);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 1a7d366..e1bca4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -38,7 +38,7 @@
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
@@ -69,7 +69,6 @@
     private QSAnimator mQSAnimator;
     private HeightListener mPanelView;
     protected QuickStatusBarHeader mHeader;
-    private QSCustomizer mQSCustomizer;
     protected NonInterceptingScrollView mQSPanelScrollView;
     private QSDetail mQSDetail;
     private boolean mListening;
@@ -97,6 +96,7 @@
     private float mLastHeaderTranslation;
     private QSPanelController mQSPanelController;
     private QuickQSPanelController mQuickQSPanelController;
+    private QSCustomizerController mQSCustomizerController;
 
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -148,16 +148,17 @@
         mQSContainerImplController.init();
         mContainer = mQSContainerImplController.getView();
 
-        mQSDetail.setQsPanel(mQSPanelController.getView(), mHeader, mFooter);
+        mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter);
         mQSAnimator = qsFragmentComponent.getQSAnimator();
 
-        mQSCustomizer = view.findViewById(R.id.qs_customize);
-        mQSCustomizer.setQs(this);
+        mQSCustomizerController = qsFragmentComponent.getQSCustomizerController();
+        mQSCustomizerController.init();
+        mQSCustomizerController.setQs(this);
         if (savedInstanceState != null) {
             setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
             setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
             setEditLocation(view);
-            mQSCustomizer.restoreInstanceState(savedInstanceState);
+            mQSCustomizerController.restoreInstanceState(savedInstanceState);
             if (mQsExpanded) {
                 mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState);
             }
@@ -181,7 +182,7 @@
         if (mListening) {
             setListening(false);
         }
-        mQSCustomizer.setQs(null);
+        mQSCustomizerController.setQs(null);
     }
 
     @Override
@@ -189,7 +190,7 @@
         super.onSaveInstanceState(outState);
         outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
         outState.putBoolean(EXTRA_LISTENING, mListening);
-        mQSCustomizer.saveInstanceState(outState);
+        mQSCustomizerController.saveInstanceState(outState);
         if (mQsExpanded) {
             mQSPanelController.getTileLayout().saveInstanceState(outState);
         }
@@ -236,23 +237,22 @@
         int[] loc = edit.getLocationOnScreen();
         int x = loc[0] + edit.getWidth() / 2;
         int y = loc[1] + edit.getHeight() / 2;
-        mQSCustomizer.setEditLocation(x, y);
+        mQSCustomizerController.setEditLocation(x, y);
     }
 
     @Override
     public void setContainer(ViewGroup container) {
         if (container instanceof NotificationsQuickSettingsContainer) {
-            mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
+            mQSCustomizerController.setContainer((NotificationsQuickSettingsContainer) container);
         }
     }
 
     @Override
     public boolean isCustomizing() {
-        return mQSCustomizer.isCustomizing();
+        return mQSCustomizerController.isCustomizing();
     }
 
     public void setHost(QSTileHost qsh) {
-        mQSPanelController.setCustomizer(mQSCustomizer);
         mHeader.setQSPanel(mQSPanelController.getView());
         mFooter.setQSPanel(mQSPanelController.getView());
         mQSDetail.setHost(qsh);
@@ -325,13 +325,9 @@
         return mQSPanelController.getView();
     }
 
-    public QSCustomizer getCustomizer() {
-        return mQSCustomizer;
-    }
-
     @Override
     public boolean isShowingDetail() {
-        return mQSPanelController.isShowingCustomize() || mQSDetail.isShowingDetail();
+        return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail();
     }
 
     @Override
@@ -553,9 +549,10 @@
     public void notifyCustomizeChanged() {
         // The customize state changed, so our height changed.
         mContainer.updateExpansion();
-        mQSPanelScrollView.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE
+        mQSPanelScrollView.setVisibility(!mQSCustomizerController.isCustomizing() ? View.VISIBLE
                 : View.INVISIBLE);
-        mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
+        mFooter.setVisibility(
+                !mQSCustomizerController.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
         // Let the panel know the position changed and it needs to update where notifications
         // and whatnot are.
         mPanelView.onQsHeightChanged();
@@ -567,7 +564,7 @@
      */
     @Override
     public int getDesiredHeight() {
-        if (mQSCustomizer.isCustomizing()) {
+        if (mQSCustomizerController.isCustomizing()) {
             return getView().getHeight();
         }
         if (mQSDetail.isClosingDetail()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 76f2446..758e0c5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -46,7 +46,6 @@
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.ToggleSliderView;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -56,7 +55,6 @@
 import com.android.systemui.util.animation.DisappearParameters;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -84,7 +82,6 @@
 
     private final H mHandler = new H();
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
-    private QSTileRevealController mQsTileRevealController;
     /** Whether or not the QS media player feature is enabled. */
     protected boolean mUsingMediaPlayer;
     private int mVisualMarginStart;
@@ -117,7 +114,6 @@
     private int mVisualTilePadding;
     private boolean mUsingHorizontalLayout;
 
-    private QSCustomizer mCustomizePanel;
     private Record mDetailRecord;
 
     private BrightnessMirrorController mBrightnessMirrorController;
@@ -186,10 +182,6 @@
 
             initMediaHostState();
         }
-        if (mRegularTileLayout instanceof PagedTileLayout) {
-            mQsTileRevealController = new QSTileRevealController(mContext, this,
-                    (PagedTileLayout) mRegularTileLayout);
-        }
         mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), "");
     }
 
@@ -297,14 +289,6 @@
         setMeasuredDimension(getMeasuredWidth(), height);
     }
 
-    public QSTileRevealController getQsTileRevealController() {
-        return mQsTileRevealController;
-    }
-
-    public boolean isShowingCustomize() {
-        return mCustomizePanel != null && mCustomizePanel.isCustomizing();
-    }
-
     @Override
     protected void onDetachedFromWindow() {
         if (mTileLayout != null) {
@@ -362,10 +346,6 @@
         mCallback = callback;
     }
 
-    void setCustomizer(QSCustomizer customizer) {
-        mCustomizePanel = customizer;
-    }
-
     /**
      * Links the footer's page indicator, which is used in landscape orientation to save space.
      *
@@ -608,12 +588,6 @@
         }
     }
 
-    public void onCollapse() {
-        if (mCustomizePanel != null && mCustomizePanel.isShown()) {
-            mCustomizePanel.hide();
-        }
-    }
-
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mQSLogger.logPanelExpanded(expanded, getDumpableTag());
@@ -684,10 +658,6 @@
         return mExpanded;
     }
 
-    void updateRevealedTiles(Collection<QSTile> tiles) {
-        mQsTileRevealController.updateRevealedTiles(tiles);
-    }
-
     void addTile(QSPanelControllerBase.TileRecord tileRecord) {
         final QSTile.Callback callback = new QSTile.Callback() {
             @Override
@@ -742,29 +712,7 @@
         mTileLayout.removeTile(tileRecord);
     }
 
-    public void showEdit(final View v) {
-        v.post(new Runnable() {
-            @Override
-            public void run() {
-                if (mCustomizePanel != null) {
-                    if (!mCustomizePanel.isCustomizing()) {
-                        int[] loc = v.getLocationOnScreen();
-                        int x = loc[0] + v.getWidth() / 2;
-                        int y = loc[1] + v.getHeight() / 2;
-                        mCustomizePanel.show(x, y);
-                    }
-                }
-
-            }
-        });
-    }
-
-    public void closeDetail() {
-        if (mCustomizePanel != null && mCustomizePanel.isShown()) {
-            // Treat this as a detail panel for now, to make things easy.
-            mCustomizePanel.hide();
-            return;
-        }
+    void closeDetail() {
         showDetail(false, mDetailRecord);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f222b0d..e9670a9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -29,7 +29,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -44,6 +44,7 @@
 public class QSPanelController extends QSPanelControllerBase<QSPanel> {
     private final QSSecurityFooter mQsSecurityFooter;
     private final TunerService mTunerService;
+    private final QSCustomizerController mQsCustomizerController;
     private final BrightnessController mBrightnessController;
 
     private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
@@ -61,18 +62,26 @@
 
     @Inject
     QSPanelController(QSPanel view, QSSecurityFooter qsSecurityFooter, TunerService tunerService,
-            QSTileHost qstileHost, DumpManager dumpManager,
-            MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+            QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
+            QSTileRevealController.Factory qsTileRevealControllerFactory,
+            DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             BrightnessController.Factory brightnessControllerFactory) {
-        super(view, qstileHost, metricsLogger, uiEventLogger, dumpManager);
+        super(view, qstileHost, qsCustomizerController, qsTileRevealControllerFactory,
+                metricsLogger, uiEventLogger, dumpManager);
         mQsSecurityFooter = qsSecurityFooter;
         mTunerService = tunerService;
+        mQsCustomizerController = qsCustomizerController;
         mQsSecurityFooter.setHostEnvironment(qstileHost);
         mBrightnessController = brightnessControllerFactory.create(
                 mView.findViewById(R.id.brightness_slider));
     }
 
     @Override
+    public void onInit() {
+        mQsCustomizerController.init();
+    }
+
+    @Override
     protected void onViewAttached() {
         super.onViewAttached();
         mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS);
@@ -115,16 +124,6 @@
     }
 
     /** */
-    public void setCustomizer(QSCustomizer customizer) {
-        mView.setCustomizer(customizer);
-    }
-
-    /** */
-    public boolean isShowingCustomize() {
-        return mView.isShowingCustomize();
-    }
-
-    /** */
     public void setVisibility(int visibility) {
         mView.setVisibility(visibility);
     }
@@ -148,11 +147,6 @@
     }
 
     /** */
-    public QSTileRevealController getQsTileRevealController() {
-        return mView.getQsTileRevealController();
-    }
-
-    /** */
     public MediaHost getMediaHost() {
         return mView.getMediaHost();
     }
@@ -196,6 +190,23 @@
 
     /** Start customizing the Quick Settings. */
     public void showEdit(View view) {
-        mView.showEdit(view);
+        view.post(() -> {
+            if (!mQsCustomizerController.isCustomizing()) {
+                int[] loc = view.getLocationOnScreen();
+                int x = loc[0] + view.getWidth() / 2;
+                int y = loc[1] + view.getHeight() / 2;
+                mQsCustomizerController.show(x, y, false);
+            }
+        });
+    }
+
+    /** */
+    public void setCallback(QSDetail.Callback qsPanelCallback) {
+        mView.setCallback(qsPanelCallback);
+    }
+
+    /** */
+    public void setGridContentVisibility(boolean visible) {
+        mView.setGridContentVisibility(visible);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 68a6cdc..0a4151b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -29,6 +29,7 @@
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.util.ViewController;
 
@@ -46,6 +47,8 @@
 public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewController<T>
         implements Dumpable{
     protected final QSTileHost mHost;
+    private final QSCustomizerController mQsCustomizerController;
+    private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
     private final MediaHost mMediaHost;
     private final MetricsLogger mMetricsLogger;
     private final UiEventLogger mUiEventLogger;
@@ -53,6 +56,7 @@
     protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
 
     private int mLastOrientation;
+    private QSTileRevealController mQsTileRevealController;
 
     private final QSHost.Callback mQSHostCallback = this::setTiles;
 
@@ -69,9 +73,13 @@
     private String mCachedSpecs = "";
 
     protected QSPanelControllerBase(T view, QSTileHost host,
+            QSCustomizerController qsCustomizerController,
+            QSTileRevealController.Factory qsTileRevealControllerFactory,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger, DumpManager dumpManager) {
         super(view);
         mHost = host;
+        mQsCustomizerController = qsCustomizerController;
+        mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
         mMediaHost = mView.getMediaHost();
         mMetricsLogger = metricsLogger;
         mUiEventLogger = uiEventLogger;
@@ -80,6 +88,12 @@
 
     @Override
     protected void onViewAttached() {
+        QSPanel.QSTileLayout regularTileLayout = mView.createRegularTileLayout();
+        if (regularTileLayout instanceof PagedTileLayout) {
+            mQsTileRevealController = mQsTileRevealControllerFactory.create(
+                    (PagedTileLayout) regularTileLayout);
+        }
+
         mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
         mHost.addCallback(mQSHostCallback);
         mMediaHost.addVisibilityChangeListener(aBoolean -> {
@@ -111,7 +125,7 @@
     /** */
     public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
         if (!collapsedView) {
-            mView.updateRevealedTiles(tiles);
+            mQsTileRevealController.updateRevealedTiles(tiles);
         }
         for (QSPanelControllerBase.TileRecord record : mRecords) {
             mView.removeTile(record);
@@ -192,6 +206,10 @@
 
     /** */
     public void closeDetail() {
+        if (mQsCustomizerController.isShown()) {
+            mQsCustomizerController.hide();
+            return;
+        }
         mView.closeDetail();
     }
 
@@ -228,6 +246,10 @@
         }
     }
 
+    /** */
+    public QSTileRevealController getQsTileRevealController() {
+        return mQsTileRevealController;
+    }
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
index 3d4a417..9414d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -8,6 +8,7 @@
 
 import com.android.systemui.Prefs;
 import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 
 import java.util.Collection;
@@ -17,13 +18,13 @@
 import javax.inject.Inject;
 
 /** */
-@QSScope
 public class QSTileRevealController {
     private static final long QS_REVEAL_TILES_DELAY = 500L;
 
     private final Context mContext;
     private final QSPanel mQSPanel;
     private final PagedTileLayout mPagedTileLayout;
+    private final QSCustomizerController mQsCustomizerController;
     private final ArraySet<String> mTilesToReveal = new ArraySet<>();
     private final Handler mHandler = new Handler();
 
@@ -38,12 +39,12 @@
             });
         }
     };
-
-    @Inject
-    QSTileRevealController(Context context, QSPanel qsPanel, PagedTileLayout pagedTileLayout) {
+    QSTileRevealController(Context context, QSPanel qsPanel, PagedTileLayout pagedTileLayout,
+            QSCustomizerController qsCustomizerController) {
         mContext = context;
         mQSPanel = qsPanel;
         mPagedTileLayout = pagedTileLayout;
+        mQsCustomizerController = qsCustomizerController;
     }
 
     public void setExpansion(float expansion) {
@@ -62,7 +63,7 @@
 
         final Set<String> revealedTiles = Prefs.getStringSet(
                 mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET);
-        if (revealedTiles.isEmpty() || mQSPanel.isShowingCustomize()) {
+        if (revealedTiles.isEmpty() || mQsCustomizerController.isCustomizing()) {
             // Do not reveal QS tiles the user has upon first load or those that they directly
             // added through customization.
             addTileSpecsToRevealed(tileSpecs);
@@ -79,4 +80,24 @@
         revealedTiles.addAll(specs);
         Prefs.putStringSet(mContext, QS_TILE_SPECS_REVEALED, revealedTiles);
     }
+
+    /** TODO(b/168904199): Remove this once QSPanel has its rejection removed. */
+    @QSScope
+    static class Factory {
+        private final Context mContext;
+        private final QSPanel mQsPanel;
+        private final QSCustomizerController mQsCustomizerController;
+
+        @Inject
+        Factory(Context context, QSPanel qsPanel, QSCustomizerController qsCustomizerController) {
+            mContext = context;
+            mQsPanel = qsPanel;
+            mQsCustomizerController = qsCustomizerController;
+        }
+
+        QSTileRevealController create(PagedTileLayout pagedTileLayout) {
+            return new QSTileRevealController(mContext, mQsPanel, pagedTileLayout,
+                    mQsCustomizerController);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 97b6e99..a718271 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -23,6 +23,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -41,9 +42,12 @@
 
     @Inject
     QuickQSPanelController(QuickQSPanel view, TunerService tunerService, QSTileHost qsTileHost,
+            QSCustomizerController qsCustomizerController,
+            QSTileRevealController.Factory qsTileRevealControllerFactory,
             MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
             DumpManager dumpManager) {
-        super(view, qsTileHost, metricsLogger, uiEventLogger, dumpManager);
+        super(view, qsTileHost, qsCustomizerController, qsTileRevealControllerFactory,
+                metricsLogger, uiEventLogger, dumpManager);
         mTunerService = tunerService;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 8097958..3291aa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -20,41 +20,23 @@
 import android.animation.AnimatorListenerAdapter;
 import android.content.Context;
 import android.content.res.Configuration;
-import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.Menu;
-import android.view.MenuItem;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.Toolbar;
-import android.widget.Toolbar.OnMenuItemClickListener;
 
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.recyclerview.widget.DefaultItemAnimator;
-import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
 import com.android.systemui.R;
-import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.qs.QSDetailClipper;
-import com.android.systemui.qs.QSEditEvent;
-import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
 
 /**
  * Allows full-screen customization of QS, through show() and hide().
@@ -62,24 +44,16 @@
  * This adds itself to the status bar window, so it can appear on top of quick settings and
  * *someday* do fancy animations to get into/out of it.
  */
-public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener {
+public class QSCustomizer extends LinearLayout {
 
-    private static final int MENU_RESET = Menu.FIRST;
-    private static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
-    private static final String TAG = "QSCustomizer";
+    static final int MENU_RESET = Menu.FIRST;
+    static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
 
     private final QSDetailClipper mClipper;
-    private final LightBarController mLightBarController;
-    private KeyguardStateController mKeyguardStateController;
-    private final ScreenLifecycle mScreenLifecycle;
-    private final TileQueryHelper mTileQueryHelper;
     private final View mTransparentView;
-    private final QSTileHost mHost;
 
     private boolean isShown;
-    private RecyclerView mRecyclerView;
-    private TileAdapter mTileAdapter;
-    private Toolbar mToolbar;
+    private final RecyclerView mRecyclerView;
     private boolean mCustomizing;
     private NotificationsQuickSettingsContainer mNotifQsContainer;
     private QS mQs;
@@ -87,90 +61,47 @@
     private int mY;
     private boolean mOpening;
     private boolean mIsShowingNavBackdrop;
-    private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
 
-    @Inject
-    public QSCustomizer(Context context, AttributeSet attrs,
-            LightBarController lightBarController,
-            KeyguardStateController keyguardStateController,
-            ScreenLifecycle screenLifecycle,
-            TileQueryHelper tileQueryHelper,
-            QSTileHost qsTileHost,
-            UiEventLogger uiEventLogger) {
+    public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
 
         LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
         mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
-        mToolbar = findViewById(com.android.internal.R.id.action_bar);
+        Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar);
         TypedValue value = new TypedValue();
         mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
-        mToolbar.setNavigationIcon(
+        toolbar.setNavigationIcon(
                 getResources().getDrawable(value.resourceId, mContext.getTheme()));
-        mToolbar.setNavigationOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                hide();
-            }
-        });
-        mToolbar.setOnMenuItemClickListener(this);
-        mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
+
+        toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
                 mContext.getString(com.android.internal.R.string.reset));
-        mToolbar.setTitle(R.string.qs_edit);
+        toolbar.setTitle(R.string.qs_edit);
         mRecyclerView = findViewById(android.R.id.list);
         mTransparentView = findViewById(R.id.customizer_transparent_view);
-        mTileAdapter = new TileAdapter(getContext(), uiEventLogger);
-        mTileQueryHelper = tileQueryHelper;
-        mTileQueryHelper.setListener(mTileAdapter);
-        mRecyclerView.setAdapter(mTileAdapter);
-        mTileAdapter.getItemTouchHelper().attachToRecyclerView(mRecyclerView);
-        GridLayoutManager layout = new GridLayoutManager(getContext(), 3) {
-            @Override
-            public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
-                    RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
-                // Do not read row and column every time it changes.
-            }
-        };
-        layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
-        mRecyclerView.setLayoutManager(layout);
-        mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
-        mRecyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
         DefaultItemAnimator animator = new DefaultItemAnimator();
         animator.setMoveDuration(TileAdapter.MOVE_DURATION);
         mRecyclerView.setItemAnimator(animator);
-        mLightBarController = lightBarController;
-        mKeyguardStateController = keyguardStateController;
-        mScreenLifecycle = screenLifecycle;
-        mHost = qsTileHost;
-        mTileAdapter.setHost(mHost);
-        updateNavBackDrop(getResources().getConfiguration());
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        updateNavBackDrop(newConfig);
-        updateResources();
-    }
-
-    private void updateResources() {
+    void updateResources() {
         LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
         lp.height = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.quick_qs_offset_height);
         mTransparentView.setLayoutParams(lp);
     }
 
-    private void updateNavBackDrop(Configuration newConfig) {
+    void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) {
         View navBackdrop = findViewById(R.id.nav_bar_background);
         mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
                 || newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
         if (navBackdrop != null) {
             navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
         }
-        updateNavColors();
+        updateNavColors(lightBarController);
     }
 
-    private void updateNavColors() {
-        mLightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
+    void updateNavColors(LightBarController lightBarController) {
+        lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
     }
 
     public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) {
@@ -184,39 +115,30 @@
     /** Animate and show QSCustomizer panel.
      * @param x,y Location on screen of {@code edit} button to determine center of animation.
      */
-    public void show(int x, int y) {
+    void show(int x, int y, TileAdapter tileAdapter) {
         if (!isShown) {
-            int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen();
+            int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
             mX = x - containerLocation[0];
             mY = y - containerLocation[1];
-            mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN);
             isShown = true;
             mOpening = true;
-            setTileSpecs();
             setVisibility(View.VISIBLE);
-            mClipper.animateCircularClip(mX, mY, true, mExpandAnimationListener);
-            queryTiles();
+            mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter));
             mNotifQsContainer.setCustomizerAnimating(true);
             mNotifQsContainer.setCustomizerShowing(true);
-            mKeyguardStateController.addCallback(mKeyguardCallback);
-            updateNavColors();
         }
     }
 
 
-    public void showImmediately() {
+    void showImmediately() {
         if (!isShown) {
             setVisibility(VISIBLE);
             mClipper.cancelAnimator();
             mClipper.showBackground();
             isShown = true;
-            setTileSpecs();
             setCustomizing(true);
-            queryTiles();
             mNotifQsContainer.setCustomizerAnimating(false);
             mNotifQsContainer.setCustomizerShowing(true);
-            mKeyguardStateController.addCallback(mKeyguardCallback);
-            updateNavColors();
         }
     }
 
@@ -225,9 +147,6 @@
      * {@link TileAdapter}.
      */
     public void setContentPaddings(int paddingStart, int paddingEnd) {
-        int halfMargin = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
-        mTileAdapter.changeHalfMargin(halfMargin);
         mRecyclerView.setPaddingRelative(
                 paddingStart,
                 mRecyclerView.getPaddingTop(),
@@ -236,22 +155,14 @@
         );
     }
 
-    private void queryTiles() {
-        mTileQueryHelper.queryTiles(mHost);
-    }
-
-    public void hide() {
-        final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
+    /** Hide the customizer. */
+    public void hide(boolean animate) {
         if (isShown) {
-            mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
             isShown = false;
-            mToolbar.dismissPopupMenus();
             mClipper.cancelAnimator();
             // Make sure we're not opening (because we're closing). Nobody can think we are
             // customizing after the next two lines.
             mOpening = false;
-            setCustomizing(false);
-            save();
             if (animate) {
                 mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
             } else {
@@ -259,8 +170,6 @@
             }
             mNotifQsContainer.setCustomizerAnimating(animate);
             mNotifQsContainer.setCustomizerShowing(false);
-            mKeyguardStateController.removeCallback(mKeyguardCallback);
-            updateNavColors();
         }
     }
 
@@ -268,7 +177,7 @@
         return isShown;
     }
 
-    private void setCustomizing(boolean customizing) {
+    void setCustomizing(boolean customizing) {
         mCustomizing = customizing;
         mQs.notifyCustomizeChanged();
     }
@@ -277,78 +186,21 @@
         return mCustomizing || mOpening;
     }
 
-    @Override
-    public boolean onMenuItemClick(MenuItem item) {
-        switch (item.getItemId()) {
-            case MENU_RESET:
-                mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET);
-                reset();
-                break;
-        }
-        return false;
-    }
-
-    private void reset() {
-        mTileAdapter.resetTileSpecs(mHost, QSTileHost.getDefaultSpecs(mContext));
-    }
-
-    private void setTileSpecs() {
-        List<String> specs = new ArrayList<>();
-        for (QSTile tile : mHost.getTiles()) {
-            specs.add(tile.getTileSpec());
-        }
-        mTileAdapter.setTileSpecs(specs);
-        mRecyclerView.setAdapter(mTileAdapter);
-    }
-
-    private void save() {
-        if (mTileQueryHelper.isFinished()) {
-            mTileAdapter.saveSpecs(mHost);
-        }
-    }
-
-
-    public void saveInstanceState(Bundle outState) {
-        if (isShown) {
-            mKeyguardStateController.removeCallback(mKeyguardCallback);
-        }
-        outState.putBoolean(EXTRA_QS_CUSTOMIZING, mCustomizing);
-    }
-
-    public void restoreInstanceState(Bundle savedInstanceState) {
-        boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING);
-        if (customizing) {
-            setVisibility(VISIBLE);
-            addOnLayoutChangeListener(new OnLayoutChangeListener() {
-                @Override
-                public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                        int oldLeft,
-                        int oldTop, int oldRight, int oldBottom) {
-                    removeOnLayoutChangeListener(this);
-                    showImmediately();
-                }
-            });
-        }
-    }
     /** @param x,y Location on screen of animation center.
      */
     public void setEditLocation(int x, int y) {
-        int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen();
+        int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
         mX = x - containerLocation[0];
         mY = y - containerLocation[1];
     }
 
-    private final Callback mKeyguardCallback = new Callback() {
-        @Override
-        public void onKeyguardShowingChanged() {
-            if (!isAttachedToWindow()) return;
-            if (mKeyguardStateController.isShowing() && !mOpening) {
-                hide();
-            }
-        }
-    };
+    class ExpandAnimatorListener extends AnimatorListenerAdapter {
+        private final TileAdapter mTileAdapter;
 
-    private final AnimatorListener mExpandAnimationListener = new AnimatorListenerAdapter() {
+        ExpandAnimatorListener(TileAdapter tileAdapter) {
+            mTileAdapter = tileAdapter;
+        }
+
         @Override
         public void onAnimationEnd(Animator animation) {
             if (isShown) {
@@ -356,6 +208,7 @@
             }
             mOpening = false;
             mNotifQsContainer.setCustomizerAnimating(false);
+            mRecyclerView.setAdapter(mTileAdapter);
         }
 
         @Override
@@ -363,7 +216,7 @@
             mOpening = false;
             mNotifQsContainer.setCustomizerAnimating(false);
         }
-    };
+    }
 
     private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
         @Override
@@ -372,7 +225,6 @@
                 setVisibility(View.GONE);
             }
             mNotifQsContainer.setCustomizerAnimating(false);
-            mRecyclerView.setAdapter(mTileAdapter);
         }
 
         @Override
@@ -383,4 +235,12 @@
             mNotifQsContainer.setCustomizerAnimating(false);
         }
     };
-}
+
+    public RecyclerView getRecyclerView() {
+        return mRecyclerView;
+    }
+
+    public boolean isOpening() {
+        return mOpening;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
new file mode 100644
index 0000000..9f4c58b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -0,0 +1,247 @@
+/*
+ * 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.customize;
+
+import static com.android.systemui.qs.customize.QSCustomizer.EXTRA_QS_CUSTOMIZING;
+import static com.android.systemui.qs.customize.QSCustomizer.MENU_RESET;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Toolbar;
+import android.widget.Toolbar.OnMenuItemClickListener;
+
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSEditEvent;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ViewController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/** {@link ViewController} for {@link QSCustomizer}. */
+@QSScope
+public class QSCustomizerController extends ViewController<QSCustomizer> {
+    private final TileQueryHelper mTileQueryHelper;
+    private final QSTileHost mQsTileHost;
+    private final TileAdapter mTileAdapter;
+    private final ScreenLifecycle mScreenLifecycle;
+    private final KeyguardStateController mKeyguardStateController;
+    private final LightBarController mLightBarController;
+    private final ConfigurationController mConfigurationController;
+    private final UiEventLogger mUiEventLogger;
+    private final Toolbar mToolbar;
+
+    private final OnMenuItemClickListener mOnMenuItemClickListener = new OnMenuItemClickListener() {
+        @Override
+        public boolean onMenuItemClick(MenuItem item) {
+            if (item.getItemId() == MENU_RESET) {
+                mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET);
+                reset();
+            }
+            return false;
+        }
+    };
+
+    private final KeyguardStateController.Callback mKeyguardCallback =
+            new KeyguardStateController.Callback() {
+        @Override
+        public void onKeyguardShowingChanged() {
+            if (!mView.isAttachedToWindow()) return;
+            if (mKeyguardStateController.isShowing() && !mView.isOpening()) {
+                hide();
+            }
+        }
+    };
+
+    private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+        @Override
+        public void onConfigChanged(Configuration newConfig) {
+            mView.updateNavBackDrop(newConfig, mLightBarController);
+            mView.updateResources();
+        }
+    };
+
+    @Inject
+    protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
+            QSTileHost qsTileHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
+            KeyguardStateController keyguardStateController, LightBarController lightBarController,
+            ConfigurationController configurationController, UiEventLogger uiEventLogger) {
+        super(view);
+        mTileQueryHelper = tileQueryHelper;
+        mQsTileHost = qsTileHost;
+        mTileAdapter = tileAdapter;
+        mScreenLifecycle = screenLifecycle;
+        mKeyguardStateController = keyguardStateController;
+        mLightBarController = lightBarController;
+        mConfigurationController = configurationController;
+        mUiEventLogger = uiEventLogger;
+
+        mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
+    }
+
+    @Override
+    protected void onViewAttached() {
+        mView.updateNavBackDrop(getResources().getConfiguration(), mLightBarController);
+
+        mConfigurationController.addCallback(mConfigurationListener);
+
+        mTileQueryHelper.setListener(mTileAdapter);
+        int halfMargin =
+                getResources().getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
+        mTileAdapter.changeHalfMargin(halfMargin);
+
+        RecyclerView recyclerView = mView.getRecyclerView();
+        recyclerView.setAdapter(mTileAdapter);
+        mTileAdapter.getItemTouchHelper().attachToRecyclerView(recyclerView);
+        GridLayoutManager layout = new GridLayoutManager(getContext(), 3) {
+            @Override
+            public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
+                    RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
+                // Do not read row and column every time it changes.
+            }
+        };
+        layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
+        recyclerView.setLayoutManager(layout);
+        recyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
+        recyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
+
+        mToolbar.setOnMenuItemClickListener(mOnMenuItemClickListener);
+        mToolbar.setNavigationOnClickListener(v -> hide());
+    }
+
+    @Override
+    protected void onViewDetached() {
+        mTileQueryHelper.setListener(null);
+        mToolbar.setOnMenuItemClickListener(null);
+        mConfigurationController.removeCallback(mConfigurationListener);
+    }
+
+
+    private void reset() {
+        mTileAdapter.resetTileSpecs(QSTileHost.getDefaultSpecs(getContext()));
+    }
+
+    public boolean isCustomizing() {
+        return mView.isCustomizing();
+    }
+
+    /** */
+    public void show(int x, int y, boolean immediate) {
+        if (!mView.isShown()) {
+            setTileSpecs();
+            if (immediate) {
+                mView.showImmediately();
+            } else {
+                mView.show(x, y, mTileAdapter);
+                mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN);
+            }
+            mTileQueryHelper.queryTiles(mQsTileHost);
+            mKeyguardStateController.addCallback(mKeyguardCallback);
+            mView.updateNavColors(mLightBarController);
+        }
+    }
+
+    /** */
+    public void setQs(QSFragment qsFragment) {
+        mView.setQs(qsFragment);
+    }
+
+    /** */
+    public void restoreInstanceState(Bundle savedInstanceState) {
+        boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING);
+        if (customizing) {
+            mView.setVisibility(View.VISIBLE);
+            mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft,
+                        int oldTop, int oldRight, int oldBottom) {
+                    mView.removeOnLayoutChangeListener(this);
+                    show(0, 0, true);
+                }
+            });
+        }
+    }
+
+    /** */
+    public void saveInstanceState(Bundle outState) {
+        if (mView.isShown()) {
+            mKeyguardStateController.removeCallback(mKeyguardCallback);
+        }
+        outState.putBoolean(EXTRA_QS_CUSTOMIZING, mView.isCustomizing());
+    }
+
+    /** */
+    public void setEditLocation(int x, int y) {
+        mView.setEditLocation(x, y);
+    }
+
+    /** */
+    public void setContainer(NotificationsQuickSettingsContainer container) {
+        mView.setContainer(container);
+    }
+
+    public boolean isShown() {
+        return mView.isShown();
+    }
+
+    /** Hice the customizer. */
+    public void hide() {
+        final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
+        if (mView.isShown()) {
+            mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
+            mToolbar.dismissPopupMenus();
+            mView.setCustomizing(false);
+            save();
+            mView.hide(animate);
+            mView.updateNavColors(mLightBarController);
+            mKeyguardStateController.removeCallback(mKeyguardCallback);
+        }
+    }
+
+    private void save() {
+        if (mTileQueryHelper.isFinished()) {
+            mTileAdapter.saveSpecs(mQsTileHost);
+        }
+    }
+
+    private void setTileSpecs() {
+        List<String> specs = new ArrayList<>();
+        for (QSTile tile : mQsTileHost.getTiles()) {
+            specs.add(tile.getTileSpec());
+        }
+        mTileAdapter.setTileSpecs(specs);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index b471dfa..dfc771b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -46,12 +46,17 @@
 import com.android.systemui.qs.customize.TileAdapter.Holder;
 import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
 import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
+import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSIconViewImpl;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
+/** */
+@QSScope
 public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
     private static final long DRAG_LENGTH = 100;
     private static final float DRAG_SCALE = 1.2f;
@@ -78,6 +83,7 @@
     private final ItemDecoration mDecoration;
     private final MarginTileDecoration mMarginDecoration;
     private final int mMinNumTiles;
+    private final QSTileHost mHost;
     private int mEditIndex;
     private int mTileDividerIndex;
     private int mFocusIndex;
@@ -89,13 +95,14 @@
     private Holder mCurrentDrag;
     private int mAccessibilityAction = ACTION_NONE;
     private int mAccessibilityFromIndex;
-    private QSTileHost mHost;
     private final UiEventLogger mUiEventLogger;
     private final AccessibilityDelegateCompat mAccessibilityDelegate;
     private RecyclerView mRecyclerView;
 
-    public TileAdapter(Context context, UiEventLogger uiEventLogger) {
+    @Inject
+    public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) {
         mContext = context;
+        mHost = qsHost;
         mUiEventLogger = uiEventLogger;
         mItemTouchHelper = new ItemTouchHelper(mCallbacks);
         mDecoration = new TileItemDecoration(context);
@@ -114,10 +121,6 @@
         mRecyclerView = null;
     }
 
-    public void setHost(QSTileHost host) {
-        mHost = host;
-    }
-
     public ItemTouchHelper getItemTouchHelper() {
         return mItemTouchHelper;
     }
@@ -154,9 +157,10 @@
         mAccessibilityAction = ACTION_NONE;
     }
 
-    public void resetTileSpecs(QSTileHost host, List<String> specs) {
+    /** */
+    public void resetTileSpecs(List<String> specs) {
         // Notify the host so the tiles get removed callbacks.
-        host.changeTiles(mCurrentSpecs, specs);
+        mHost.changeTiles(mCurrentSpecs, specs);
         setTileSpecs(specs);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index b795a5f..59490c6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -37,6 +37,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
 import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.external.CustomTile;
 import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
 import com.android.systemui.settings.UserTracker;
@@ -50,6 +51,8 @@
 
 import javax.inject.Inject;
 
+/** */
+@QSScope
 public class TileQueryHelper {
     private static final String TAG = "TileQueryHelper";
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
index 51b2c8d..8cc0502 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -22,6 +22,7 @@
 import com.android.systemui.qs.QSFragment;
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.qs.QuickQSPanelController;
+import com.android.systemui.qs.customize.QSCustomizerController;
 
 import dagger.BindsInstance;
 import dagger.Subcomponent;
@@ -32,6 +33,7 @@
 @Subcomponent(modules = {QSFragmentModule.class})
 @QSScope
 public interface QSFragmentComponent {
+
     /** Factory for building a {@link QSFragmentComponent}. */
     @Subcomponent.Factory
     interface Factory {
@@ -52,4 +54,7 @@
 
     /** Construct a {@link QSFooter} */
     QSFooter getQSFooter();
+
+    /** Construct a {@link QSCustomizerController}. */
+    QSCustomizerController getQSCustomizerController();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index 4bf4eff..354b2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
 import com.android.systemui.qs.QuickStatusBarHeader;
+import com.android.systemui.qs.customize.QSCustomizer;
 
 import dagger.Binds;
 import dagger.Module;
@@ -87,4 +88,11 @@
         qsFooterViewController.init();
         return qsFooterViewController;
     }
+
+    /** */
+    @Provides
+    @QSScope
+    static QSCustomizer providesQSCutomizer(@RootView View view) {
+        return view.findViewById(R.id.qs_customize);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 260f557..0dde931 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -27,7 +27,6 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.Notification;
-import android.app.WindowContext;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -43,6 +42,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -58,8 +58,10 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.R;
+import com.android.systemui.util.DeviceConfigProxy;
 
 import java.util.List;
 import java.util.function.Consumer;
@@ -143,6 +145,8 @@
     private final DisplayMetrics mDisplayMetrics;
     private final AccessibilityManager mAccessibilityManager;
     private final MediaActionSound mCameraSound;
+    private final ScrollCaptureClient mScrollCaptureClient;
+    private final DeviceConfigProxy mConfigProxy;
 
     private final Binder mWindowToken;
     private ScreenshotView mScreenshotView;
@@ -173,11 +177,16 @@
     };
 
     @Inject
-    ScreenshotController(Context context, ScreenshotSmartActions screenshotSmartActions,
+    ScreenshotController(
+            Context context,
+            ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController screenshotNotificationsController,
-            UiEventLogger uiEventLogger) {
+            ScrollCaptureClient scrollCaptureClient,
+            UiEventLogger uiEventLogger,
+            DeviceConfigProxy configProxy) {
         mScreenshotSmartActions = screenshotSmartActions;
         mNotificationsController = screenshotNotificationsController;
+        mScrollCaptureClient = scrollCaptureClient;
         mUiEventLogger = uiEventLogger;
 
         final DisplayManager dm = requireNonNull(context.getSystemService(DisplayManager.class));
@@ -186,6 +195,7 @@
         mWindowManager = mContext.getSystemService(WindowManager.class);
 
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+        mConfigProxy = configProxy;
 
         reloadAssets();
         Configuration config = mContext.getResources().getConfiguration();
@@ -193,6 +203,7 @@
         mDirectionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
         mOrientationPortrait = config.orientation == ORIENTATION_PORTRAIT;
         mWindowToken = new Binder("ScreenshotController");
+        mScrollCaptureClient.setHostWindowToken(mWindowToken);
 
         // Setup the window that we are going to use
         mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -455,6 +466,19 @@
 
         // Start the post-screenshot animation
         startAnimation(finisher, screenRect, screenInsets, showFlash);
+
+        if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
+            mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) ->
+                    mScreenshotView.showScrollChip(() ->
+                            runScrollCapture(connection,
+                                    () -> dismissScreenshot(false))));
+        }
+    }
+
+    private void runScrollCapture(ScrollCaptureClient.Connection connection,
+            Runnable after) {
+        new ScrollCaptureController(mContext, connection).run(after);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 29f6e8b..3383f80 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -113,6 +113,7 @@
     private FrameLayout mDismissButton;
     private ScreenshotActionChip mShareChip;
     private ScreenshotActionChip mEditChip;
+    private ScreenshotActionChip mScrollChip;
 
     private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
     private PendingInteraction mPendingInteraction;
@@ -152,6 +153,20 @@
         mContext.getDisplay().getRealMetrics(mDisplayMetrics);
     }
 
+    /**
+     * Called to display the scroll action chip when support is detected.
+     *
+     * @param onClick the action to take when the chip is clicked.
+     */
+    public void showScrollChip(Runnable onClick) {
+        mScrollChip.setVisibility(VISIBLE);
+        mScrollChip.setOnClickListener((v) ->
+                onClick.run()
+                // TODO Logging, store event consumer to a field
+                //onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
+        );
+    }
+
     @Override // ViewTreeObserver.OnComputeInternalInsetsListener
     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
         inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
@@ -193,6 +208,7 @@
         mScreenshotSelectorView = requireNonNull(findViewById(R.id.global_screenshot_selector));
         mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
         mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
+        mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
 
         mScreenshotPreview.setClipToOutline(true);
         mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
@@ -387,7 +403,7 @@
         });
         chips.add(mShareChip);
 
-        mEditChip.setText(mContext.getString(com.android.internal.R.string.screenshot_edit));
+        mEditChip.setText(mContext.getString(R.string.screenshot_edit_label));
         mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
         mEditChip.setOnClickListener(v -> {
             mEditChip.setIsPending(true);
@@ -402,6 +418,11 @@
             mPendingInteraction = PendingInteraction.PREVIEW;
         });
 
+        mScrollChip.setText(mContext.getString(R.string.screenshot_scroll_label));
+        mScrollChip.setIcon(Icon.createWithResource(mContext,
+                R.drawable.ic_screenshot_scroll), true);
+        chips.add(mScrollChip);
+
         // remove the margin from the last chip so that it's correctly aligned with the end
         LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
                 mActionsView.getChildAt(0).getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
new file mode 100644
index 0000000..ea835fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -0,0 +1,346 @@
+/*
+ * 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.screenshot;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.UiContext;
+import android.app.ActivityTaskManager;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureConnection;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.view.ScrollCaptureViewSupport;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * High level interface to scroll capture API.
+ */
+public class ScrollCaptureClient {
+
+    @VisibleForTesting
+    static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID;
+
+    private static final String TAG = "ScrollCaptureClient";
+
+    /** Whether to log method names and arguments for most calls */
+    private static final boolean DEBUG_TRACE = false;
+
+    /**
+     * A connection to a remote window. Starts a capture session.
+     */
+    public interface Connection {
+        /**
+         * Session start should be deferred until UI is active because of resource allocation and
+         * potential visible side effects in the target window.
+         *
+         * @param maxBuffers the maximum number of buffers (tiles) that may be in use at one
+         *                   time, tiles are not cached anywhere so set this to a large enough
+         *                   number to retain offscreen content until it is no longer needed
+         * @param sessionConsumer listener to receive the session once active
+         */
+        void start(int maxBuffers, Consumer<Session> sessionConsumer);
+
+        /**
+         * Close the connection.
+         */
+        void close();
+    }
+
+    static class CaptureResult {
+        public final Image image;
+        /**
+         * The area requested, in content rect space, relative to scroll-bounds.
+         */
+        public final Rect requested;
+        /**
+         * The actual area captured, in content rect space, relative to scroll-bounds. This may be
+         * cropped or empty depending on available content.
+         */
+        public final Rect captured;
+
+        // Error?
+
+        private CaptureResult(Image image, Rect request, Rect captured) {
+            this.image =  image;
+            this.requested = request;
+            this.captured = captured;
+        }
+    }
+
+    /**
+     * Represents the connection to a target window and provides a mechanism for requesting tiles.
+     */
+    interface Session {
+        /**
+         * Request the given horizontal strip. Values are y-coordinates in captured space, relative
+         * to start position.
+         *
+         * @param contentRect the area to capture, in content rect space, relative to scroll-bounds
+         * @param consumer listener to be informed of the result
+         */
+        void requestTile(Rect contentRect, Consumer<CaptureResult> consumer);
+
+        /**
+         * End the capture session, return the target app to original state. The returned
+         * stage must be waited for to complete to allow the target app a chance to restore to
+         * original state before becoming visible.
+         *
+         * @return a stage presenting the session shutdown
+         */
+        void end(Runnable listener);
+
+        int getMaxTileHeight();
+
+        int getMaxTileWidth();
+    }
+
+    private final IWindowManager mWindowManagerService;
+    private IBinder mHostWindowToken;
+
+    @Inject
+    public ScrollCaptureClient(@UiContext Context context, IWindowManager windowManagerService) {
+        requireNonNull(context.getDisplay(), "context must be associated with a Display!");
+        mWindowManagerService = windowManagerService;
+    }
+
+    public void setHostWindowToken(IBinder token) {
+        mHostWindowToken = token;
+    }
+
+    /**
+     * Check for scroll capture support.
+     *
+     * @param displayId id for the display containing the target window
+     * @param consumer receives a connection when available
+     */
+    public void request(int displayId, Consumer<Connection> consumer) {
+        request(displayId, MATCH_ANY_TASK, consumer);
+    }
+
+    /**
+     * Check for scroll capture support.
+     *
+     * @param displayId id for the display containing the target window
+     * @param taskId id for the task containing the target window or {@link #MATCH_ANY_TASK}.
+     * @param consumer receives a connection when available
+     */
+    public void request(int displayId, int taskId, Consumer<Connection> consumer) {
+        try {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "requestScrollCapture(displayId=" + displayId + ", " + mHostWindowToken
+                        + ", taskId=" + taskId + ", consumer=" + consumer + ")");
+            }
+            mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId,
+                    new ControllerCallbacks(consumer));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Ignored remote exception", e);
+        }
+    }
+
+    private static class ControllerCallbacks extends IScrollCaptureCallbacks.Stub implements
+            Connection, Session, IBinder.DeathRecipient {
+
+        private IScrollCaptureConnection mConnection;
+        private Consumer<Connection> mConnectionConsumer;
+        private Consumer<Session> mSessionConsumer;
+        private Consumer<CaptureResult> mResultConsumer;
+        private Runnable mShutdownListener;
+
+        private ImageReader mReader;
+        private Rect mScrollBounds;
+        private Rect mRequestRect;
+        private boolean mStarted;
+
+        private ControllerCallbacks(Consumer<Connection> connectionConsumer) {
+            mConnectionConsumer = connectionConsumer;
+        }
+
+        // IScrollCaptureCallbacks
+
+        @Override
+        public void onConnected(IScrollCaptureConnection connection, Rect scrollBounds,
+                Point positionInWindow) throws RemoteException {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "onConnected(connection=" + connection + ", scrollBounds=" + scrollBounds
+                        + ", positionInWindow=" + positionInWindow + ")");
+            }
+            mConnection = connection;
+            mConnection.asBinder().linkToDeath(this, 0);
+            mScrollBounds = scrollBounds;
+            mConnectionConsumer.accept(this);
+            mConnectionConsumer = null;
+        }
+
+        @Override
+        public void onUnavailable() throws RemoteException {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "onUnavailable");
+            }
+            // The targeted app does not support scroll capture
+            // or the window could not be found... etc etc.
+        }
+
+        @Override
+        public void onCaptureStarted() {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "onCaptureStarted()");
+            }
+            mSessionConsumer.accept(this);
+            mSessionConsumer = null;
+        }
+
+        @Override
+        public void onCaptureBufferSent(long frameNumber, Rect contentArea) {
+            Image image = null;
+            if (frameNumber != ScrollCaptureViewSupport.NO_FRAME_PRODUCED) {
+                image = mReader.acquireNextImage();
+            }
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "onCaptureBufferSent(frameNumber=" + frameNumber
+                        + ", contentArea=" + contentArea + ") image=" + image);
+            }
+            // Save and clear first, since the consumer will likely request the next
+            // tile, otherwise the new consumer will be wiped out.
+            Consumer<CaptureResult> consumer = mResultConsumer;
+            mResultConsumer = null;
+            consumer.accept(new CaptureResult(image, mRequestRect, contentArea));
+        }
+
+        @Override
+        public void onConnectionClosed() {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "onConnectionClosed()");
+            }
+            disconnect();
+            if (mShutdownListener != null) {
+                mShutdownListener.run();
+                mShutdownListener = null;
+            }
+        }
+
+        // Misc
+
+        private void disconnect() {
+            if (mConnection != null) {
+                mConnection.asBinder().unlinkToDeath(this, 0);
+            }
+            mConnection = null;
+        }
+
+        // ScrollCaptureController.Connection
+
+        // -> Error handling: BiConsumer<Session, Throwable> ?
+        @Override
+        public void start(int maxBufferCount, Consumer<Session> sessionConsumer) {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "start(maxBufferCount=" + maxBufferCount
+                        + ", sessionConsumer=" + sessionConsumer + ")");
+            }
+            mReader = ImageReader.newInstance(mScrollBounds.width(), mScrollBounds.height(),
+                    PixelFormat.RGBA_8888, maxBufferCount, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+            mSessionConsumer = sessionConsumer;
+            try {
+                mConnection.startCapture(mReader.getSurface());
+                mStarted = true;
+            } catch (RemoteException e) {
+                Log.w(TAG, "should not be happening :-(");
+                // ?
+                //mSessionListener.onError(e);
+                //mSessionListener = null;
+            }
+        }
+
+        @Override
+        public void close() {
+            end(null);
+        }
+
+        // ScrollCaptureController.Session
+
+        @Override
+        public void end(Runnable listener) {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "end(listener=" + listener + ")");
+            }
+            if (mStarted) {
+                mShutdownListener = listener;
+                try {
+                    // listener called from onConnectionClosed callback
+                    mConnection.endCapture();
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Ignored exception from endCapture()", e);
+                    disconnect();
+                    listener.run();
+                }
+            } else {
+                disconnect();
+                listener.run();
+            }
+        }
+
+        @Override
+        public int getMaxTileHeight() {
+            return mScrollBounds.height();
+        }
+
+        @Override
+        public int getMaxTileWidth() {
+            return mScrollBounds.width();
+        }
+
+        @Override
+        public void requestTile(Rect contentRect, Consumer<CaptureResult> consumer) {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "requestTile(contentRect=" + contentRect + "consumer=" + consumer + ")");
+            }
+            mRequestRect = new Rect(contentRect);
+            mResultConsumer = consumer;
+            try {
+                mConnection.requestImage(mRequestRect);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Caught remote exception from requestImage", e);
+                // ?
+            }
+        }
+
+        /**
+         * The process hosting the window went away abruptly!
+         */
+        @Override
+        public void binderDied() {
+            if (DEBUG_TRACE) {
+                Log.d(TAG, "binderDied()");
+            }
+            disconnect();
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 5ced40c..800d679 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,46 +16,231 @@
 
 package com.android.systemui.screenshot;
 
-import android.os.IBinder;
-import android.view.IWindowManager;
+import static android.graphics.ColorSpace.Named.SRGB;
 
-import javax.inject.Inject;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorSpace;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.media.ExifInterface;
+import android.media.Image;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
+import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Consumer;
 
 /**
- * Stub
+ * Interaction controller between the UI and ScrollCaptureClient.
  */
 public class ScrollCaptureController {
+    private static final String TAG = "ScrollCaptureController";
 
-    public static final int STATUS_A = 0;
-    public static final int STATUS_B = 1;
+    public static final int MAX_PAGES = 5;
+    public static final int MAX_HEIGHT = 12000;
 
-    private final IWindowManager mWindowManagerService;
-    private StatusListener mListener;
+    private final Connection mConnection;
+    private final Context mContext;
+    private Picture mPicture;
 
-    /**
-     *
-     * @param windowManagerService
-     */
-    @Inject
-    public ScrollCaptureController(IWindowManager windowManagerService) {
-        mWindowManagerService = windowManagerService;
-    }
-
-    interface StatusListener {
-        void onScrollCaptureStatus(boolean available);
+    public ScrollCaptureController(Context context, Connection connection) {
+        mContext = context;
+        mConnection = connection;
     }
 
     /**
+     * Run scroll capture!
      *
-     * @param window
-     * @param listener
+     * @param after action to take after the flow is complete
      */
-    public void getStatus(IBinder window, StatusListener listener) {
-        mListener = listener;
-//        try {
-//           mWindowManagerService.requestScrollCapture(window, new ClientCallbacks());
-//        } catch (RemoteException e) {
-//        }
+    public void run(final Runnable after) {
+        mConnection.start(MAX_PAGES, (session) -> startCapture(session, after));
     }
 
+    private void startCapture(Session session, final Runnable after) {
+        Rect requestRect = new Rect(0, 0,
+                session.getMaxTileWidth(), session.getMaxTileHeight());
+        Consumer<ScrollCaptureClient.CaptureResult> consumer =
+                new Consumer<ScrollCaptureClient.CaptureResult>() {
+
+                    int mFrameCount = 0;
+
+                    @Override
+                    public void accept(ScrollCaptureClient.CaptureResult result) {
+                        mFrameCount++;
+                        boolean emptyFrame = result.captured.height() == 0;
+                        if (!emptyFrame) {
+                            mPicture = stackBelow(mPicture, result.image, result.captured.width(),
+                                    result.captured.height());
+                        }
+                        if (emptyFrame || mFrameCount > MAX_PAGES
+                                || requestRect.bottom > MAX_HEIGHT) {
+                            Uri uri = null;
+                            if (mPicture != null) {
+                                // This is probably on a binder thread right now ¯\_(ツ)_/¯
+                                uri = writeImage(Bitmap.createBitmap(mPicture));
+                                // Release those buffers!
+                                mPicture.close();
+                            }
+                            if (uri != null) {
+                                launchViewer(uri);
+                            } else {
+                                Toast.makeText(mContext, "Failed to create tall screenshot",
+                                        Toast.LENGTH_SHORT).show();
+                            }
+                            session.end(after); // end session, close connection, after.run()
+                            return;
+                        }
+                        requestRect.offset(0, session.getMaxTileHeight());
+                        session.requestTile(requestRect, /* consumer */ this);
+                    }
+                };
+
+        // fire it up!
+        session.requestTile(requestRect, consumer);
+    };
+
+
+    /**
+     * Combine the top {@link Picture} with an {@link Image} by appending the image directly
+     * below, creating a result that is the combined height of both.
+     * <p>
+     * Note: no pixel data is transferred here, only a record of drawing commands. Backing
+     * hardware buffers must not be modified/recycled until the picture is
+     * {@link Picture#close closed}.
+     *
+     * @param top the existing picture
+     * @param below the image to append below
+     * @param cropWidth the width of the pixel data to use from the image
+     * @param cropHeight the height of the pixel data to use from the image
+     *
+     * @return a new Picture which draws the previous picture with the image below it
+     */
+    private static Picture stackBelow(Picture top, Image below, int cropWidth, int cropHeight) {
+        int width = cropWidth;
+        int height = cropHeight;
+        if (top != null) {
+            height += top.getHeight();
+            width = Math.max(width, top.getWidth());
+        }
+        Picture combined = new Picture();
+        Canvas canvas = combined.beginRecording(width, height);
+        int y = 0;
+        if (top != null) {
+            canvas.drawPicture(top, new Rect(0, 0, top.getWidth(), top.getHeight()));
+            y += top.getHeight();
+        }
+        canvas.drawBitmap(Bitmap.wrapHardwareBuffer(
+                below.getHardwareBuffer(), ColorSpace.get(SRGB)), 0, y, null);
+        combined.endRecording();
+        return combined;
+    }
+
+    Uri writeImage(Bitmap image) {
+        ContentResolver resolver = mContext.getContentResolver();
+        long mImageTime = System.currentTimeMillis();
+        String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
+        String mImageFileName = String.format("tall_Screenshot_%s.png", imageDate);
+        String mScreenshotId = String.format("Screenshot_%s", UUID.randomUUID());
+        try {
+            // Save the screenshot to the MediaStore
+            final ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+                    + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+            values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName);
+            values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
+            values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000);
+            values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000);
+            values.put(
+                    MediaStore.MediaColumns.DATE_EXPIRES,
+                    (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
+            values.put(MediaStore.MediaColumns.IS_PENDING, 1);
+
+            final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                    values);
+            try {
+                try (OutputStream out = resolver.openOutputStream(uri)) {
+                    if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
+                        throw new IOException("Failed to compress");
+                    }
+                }
+
+                // Next, write metadata to help index the screenshot
+                try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
+                    final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
+
+                    exif.setAttribute(ExifInterface.TAG_SOFTWARE,
+                            "Android " + Build.DISPLAY);
+
+                    exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,
+                            Integer.toString(image.getWidth()));
+                    exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,
+                            Integer.toString(image.getHeight()));
+
+                    final ZonedDateTime time = ZonedDateTime.ofInstant(
+                            Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault());
+                    exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL,
+                            DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time));
+                    exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
+                            DateTimeFormatter.ofPattern("SSS").format(time));
+
+                    if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) {
+                        exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00");
+                    } else {
+                        exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
+                                DateTimeFormatter.ofPattern("XXX").format(time));
+                    }
+                    exif.saveAttributes();
+                }
+
+                // Everything went well above, publish it!
+                values.clear();
+                values.put(MediaStore.MediaColumns.IS_PENDING, 0);
+                values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
+                resolver.update(uri, values, null, null);
+                return uri;
+            } catch (Exception e) {
+                resolver.delete(uri, null);
+                throw e;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "unable to save screenshot", e);
+        }
+        return null;
+    }
+
+    void launchViewer(Uri uri) {
+        Intent editIntent = new Intent(Intent.ACTION_VIEW);
+        editIntent.setType("image/png");
+        editIntent.setData(uri);
+        editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivityAsUser(editIntent, UserHandle.CURRENT);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index a92b9e4..9bd34ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -26,7 +26,6 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.dagger.StatusBarModule;
@@ -43,6 +42,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.Assert;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index cee9c70..efd0519 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,7 +21,6 @@
 import android.os.Handler;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.media.MediaDataManager;
@@ -62,6 +61,7 @@
 import com.android.systemui.tracing.ProtoTracer;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 382715a..45e8098 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification;
 
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -32,6 +34,7 @@
 import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
 import android.view.View;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -226,10 +229,27 @@
                     }
                 });
                 anim.addListener(new AnimatorListenerAdapter() {
+                    private boolean mWasCancelled;
+
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_APP_START);
+                    }
+
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        mWasCancelled = true;
+                    }
+
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         setExpandAnimationRunning(false);
                         invokeCallback(iRemoteAnimationFinishedCallback);
+                        if (!mWasCancelled) {
+                            InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_APP_START);
+                        } else {
+                            InteractionJankMonitor.getInstance().cancel(CUJ_NOTIFICATION_APP_START);
+                        }
                     }
                 });
                 anim.start();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index eee9cc6..967524c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -16,9 +16,7 @@
 
 package com.android.systemui.statusbar.notification;
 
-import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
-import android.util.Log;
 import android.util.Property;
 import android.view.View;
 
@@ -34,9 +32,14 @@
 
     public static final AnimatableProperty X = AnimatableProperty.from(View.X,
             R.id.x_animator_tag, R.id.x_animator_tag_start_value, R.id.x_animator_tag_end_value);
+
     public static final AnimatableProperty Y = AnimatableProperty.from(View.Y,
             R.id.y_animator_tag, R.id.y_animator_tag_start_value, R.id.y_animator_tag_end_value);
 
+    public static final AnimatableProperty TRANSLATION_X = AnimatableProperty.from(
+            View.TRANSLATION_X, R.id.x_animator_tag, R.id.x_animator_tag_start_value,
+            R.id.x_animator_tag_end_value);
+
     /**
      * Similar to X, however this doesn't allow for any other modifications other than from this
      * property. When using X, it's possible that the view is laid out during the animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index 7d8979c..aef01e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -22,10 +22,10 @@
 import android.view.View;
 
 import com.android.systemui.DejankUtils;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.Optional;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 83a569b..29a030f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -26,6 +24,8 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.HashSet;
 import java.util.Optional;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 36adfac..3db5440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -22,7 +22,6 @@
 import android.util.Log;
 
 import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -34,6 +33,7 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 049b471..52b9b06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -17,13 +17,13 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.Bubbles
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.NotificationActivityStarter
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.StatusBar
+import com.android.wm.shell.bubbles.Bubbles
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.Optional
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 45a5d10..8f352ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.Bubbles
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.FeatureFlags
@@ -41,6 +40,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.statusbar.policy.RemoteInputUriController
+import com.android.wm.shell.bubbles.Bubbles
 import dagger.Lazy
 import java.io.FileDescriptor
 import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 7569c1b..d0e68bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.Bubbles
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.NotificationListener
 import com.android.systemui.statusbar.NotificationPresenter
@@ -25,6 +24,7 @@
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.StatusBar
+import com.android.wm.shell.bubbles.Bubbles
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.Optional
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 094e866..10273cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -33,6 +33,7 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.internal.jank.InteractionJankMonitor;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
@@ -750,12 +751,16 @@
                 if (!mWasCancelled) {
                     enableAppearDrawing(false);
                     onAppearAnimationFinished(isAppearing);
+                    InteractionJankMonitor.getInstance().end(getCujType(isAppearing));
+                } else {
+                    InteractionJankMonitor.getInstance().cancel(getCujType(isAppearing));
                 }
             }
 
             @Override
             public void onAnimationStart(Animator animation) {
                 mWasCancelled = false;
+                InteractionJankMonitor.getInstance().begin(getCujType(isAppearing));
             }
 
             @Override
@@ -766,6 +771,18 @@
         mAppearAnimator.start();
     }
 
+    private int getCujType(boolean isAppearing) {
+        if (mIsHeadsUpAnimation) {
+            return isAppearing
+                    ? InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR
+                    : InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
+        } else {
+            return isAppearing
+                    ? InteractionJankMonitor.CUJ_NOTIFICATION_ADD
+                    : InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE;
+        }
+    }
+
     protected void onAppearAnimationFinished(boolean wasAppearing) {
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 280c525..a011d36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -35,6 +35,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.Path;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
@@ -43,6 +44,7 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
 import android.util.AttributeSet;
@@ -327,6 +329,7 @@
     private boolean mShelfIconVisible;
     private boolean mAboveShelf;
     private OnUserInteractionCallback mOnUserInteractionCallback;
+    private NotificationGutsManager mNotificationGutsManager;
     private boolean mIsLowPriority;
     private boolean mIsColorized;
     private boolean mUseIncreasedCollapsedHeight;
@@ -1089,6 +1092,13 @@
         };
     }
 
+    /** The click listener for the snooze button. */
+    public View.OnClickListener getSnoozeClickListener(MenuItem item) {
+        return v -> {
+            mNotificationGutsManager.openGuts(this, 0, 0, item);
+        };
+    }
+
     private void updateClickAndFocus() {
         boolean normalChild = !isChildInGroup() || isGroupExpanded();
         boolean clickable = mOnClickListener != null && normalChild;
@@ -1155,10 +1165,11 @@
      */
     @Nullable
     public NotificationMenuRowPlugin createMenu() {
-        if (mMenuRow == null) {
+        final boolean removeShelf = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_NEW_NOTIF_DISMISS, 0 /* show shelf by default */) == 1;
+        if (mMenuRow == null || removeShelf) {
             return null;
         }
-
         if (mMenuRow.getMenuView() == null) {
             mMenuRow.createMenu(this, mEntry.getSbn());
             mMenuRow.setAppName(mAppName);
@@ -1555,7 +1566,8 @@
             StatusBarStateController statusBarStateController,
             PeopleNotificationIdentifier peopleNotificationIdentifier,
             OnUserInteractionCallback onUserInteractionCallback,
-            Optional<BubblesManager> bubblesManagerOptional) {
+            Optional<BubblesManager> bubblesManagerOptional,
+            NotificationGutsManager gutsManager) {
         mEntry = entry;
         mAppName = appName;
         if (mMenuRow == null) {
@@ -1584,6 +1596,7 @@
         }
         mOnUserInteractionCallback = onUserInteractionCallback;
         mBubblesManagerOptional = bubblesManagerOptional;
+        mNotificationGutsManager = gutsManager;
 
         cacheIsSystemNotification();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 05b1dba..cb2af54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -154,7 +154,8 @@
                 mStatusBarStateController,
                 mPeopleNotificationIdentifier,
                 mOnUserInteractionCallback,
-                mBubblesManagerOptional
+                mBubblesManagerOptional,
+                mNotificationGutsManager
         );
         mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
         if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 71e1d12..79c3007 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -18,12 +18,14 @@
 
 
 import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
+import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -31,6 +33,7 @@
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -44,6 +47,7 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.TransformableView;
@@ -458,7 +462,7 @@
         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
         if (mContainingNotification != null) {
-            applyBubbleAction(mExpandedChild, mContainingNotification.getEntry());
+            applySystemActions(mExpandedChild, mContainingNotification.getEntry());
         }
     }
 
@@ -500,7 +504,7 @@
         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
         if (mContainingNotification != null) {
-            applyBubbleAction(mHeadsUpChild, mContainingNotification.getEntry());
+            applySystemActions(mHeadsUpChild, mContainingNotification.getEntry());
         }
     }
 
@@ -1161,8 +1165,8 @@
         mForceSelectNextLayout = true;
         mPreviousExpandedRemoteInputIntent = null;
         mPreviousHeadsUpRemoteInputIntent = null;
-        applyBubbleAction(mExpandedChild, entry);
-        applyBubbleAction(mHeadsUpChild, entry);
+        applySystemActions(mExpandedChild, entry);
+        applySystemActions(mHeadsUpChild, entry);
     }
 
     private void updateAllSingleLineViews() {
@@ -1340,6 +1344,14 @@
                 NOTIFICATION_BUBBLES, 0) == 1;
     }
 
+    /**
+     * Setup icon buttons provided by System UI.
+     */
+    private void applySystemActions(View layout, NotificationEntry entry) {
+        applySnoozeAction(layout);
+        applyBubbleAction(layout, entry);
+    }
+
     private void applyBubbleAction(View layout, NotificationEntry entry) {
         if (layout == null || mContainingNotification == null || mPeopleIdentifier == null) {
             return;
@@ -1359,8 +1371,8 @@
                 && entry.getBubbleMetadata() != null;
         if (showButton) {
             Drawable d = mContext.getResources().getDrawable(entry.isBubble()
-                    ? R.drawable.ic_stop_bubble
-                    : R.drawable.ic_create_bubble);
+                    ? R.drawable.bubble_ic_stop_bubble
+                    : R.drawable.bubble_ic_create_bubble);
             mContainingNotification.updateNotificationColor();
             final int tint = mContainingNotification.getNotificationColor();
             d.setTint(tint);
@@ -1387,6 +1399,45 @@
         }
     }
 
+    private void applySnoozeAction(View layout) {
+        if (layout == null || mContainingNotification == null) {
+            return;
+        }
+        ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button);
+        View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
+        LinearLayout actionContainerLayout =
+                layout.findViewById(com.android.internal.R.id.actions_container_layout);
+        if (snoozeButton == null || actionContainer == null || actionContainerLayout == null) {
+            return;
+        }
+        final boolean showSnooze = Settings.Secure.getInt(mContext.getContentResolver(),
+                SHOW_NOTIFICATION_SNOOZE, 0) == 1;
+        if (!showSnooze) {
+            snoozeButton.setVisibility(GONE);
+            return;
+        }
+
+        Resources res = mContext.getResources();
+        Drawable snoozeDrawable = res.getDrawable(R.drawable.ic_snooze);
+        mContainingNotification.updateNotificationColor();
+        snoozeDrawable.setTint(mContainingNotification.getNotificationColor());
+        snoozeButton.setImageDrawable(snoozeDrawable);
+
+        final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext)
+                .inflate(R.layout.notification_snooze, null, false);
+        final String snoozeDescription = res.getString(
+                R.string.notification_menu_snooze_description);
+        final NotificationMenuRowPlugin.MenuItem snoozeMenuItem =
+                new NotificationMenuRow.NotificationMenuItem(
+                        mContext, snoozeDescription, snoozeGuts, R.drawable.ic_snooze);
+        snoozeButton.setContentDescription(
+                mContext.getResources().getString(R.string.notification_menu_snooze_description));
+        snoozeButton.setOnClickListener(
+                mContainingNotification.getSnoozeClickListener(snoozeMenuItem));
+        snoozeButton.setVisibility(VISIBLE);
+        actionContainer.setVisibility(VISIBLE);
+    }
+
     private void applySmartReplyView(
             SmartRepliesAndActions smartRepliesAndActions,
             NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index bd22893..5c225e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -257,7 +257,7 @@
         // TODO(b/12836565) - prototyping only adjustment
         if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
             // This will keep the clock at the top for AOD
-            darkAmount = 0f;
+            return (int) (clockY + burnInPreventionOffsetY() + mEmptyDragAmount);
         }
 
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index 5e883be..289ff71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -39,7 +39,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.systemui.R;
-import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
@@ -77,7 +76,6 @@
     private final KeyguardStateController mKeyguardStateController;
     private final Resources mResources;
     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
-    private final AuthController mAuthController;
     private boolean mKeyguardShowing;
     private boolean mKeyguardJustShown;
     private boolean mBlockUpdates;
@@ -326,8 +324,7 @@
             @Nullable DockManager dockManager,
             KeyguardStateController keyguardStateController,
             @Main Resources resources,
-            HeadsUpManagerPhone headsUpManagerPhone,
-            AuthController authController) {
+            HeadsUpManagerPhone headsUpManagerPhone) {
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
@@ -342,7 +339,6 @@
         mKeyguardStateController = keyguardStateController;
         mResources = resources;
         mHeadsUpManagerPhone = headsUpManagerPhone;
-        mAuthController = authController;
 
         mKeyguardIndicationController.setLockIconController(this);
     }
@@ -508,7 +504,7 @@
      * @return true if the visibility changed
      */
     private boolean updateIconVisibility() {
-        if (mAuthController.isUdfpsEnrolled()) {
+        if (mKeyguardUpdateMonitor.isUdfpsEnrolled()) {
             boolean changed = mLockIcon.getVisibility() == GONE;
             mLockIcon.setVisibility(GONE);
             return changed;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index f2ae3da7..ac91b70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -19,7 +19,6 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeController;
@@ -32,10 +31,14 @@
 import com.android.systemui.statusbar.NotificationShelfController;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -175,6 +178,16 @@
         updateIconLayoutParams(mContext);
     }
 
+    /**
+     * Update position of the view, with optional animation
+     */
+    public void updatePosition(int x, AnimationProperties props, boolean animate) {
+        if (mAodIcons != null) {
+            PropertyAnimator.setProperty(mAodIcons, AnimatableProperty.TRANSLATION_X, x, props,
+                    animate);
+        }
+    }
+
     public void setupShelf(NotificationShelfController notificationShelfController) {
         mShelfIcons = notificationShelfController.getShelfIcons();
         notificationShelfController.setCollapsedIcons(mNotificationIcons);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index e9a7132..231d157 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -873,7 +873,7 @@
                     clockPreferredY, hasCustomClock(),
                     hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
                     bypassEnabled, getUnlockedStackScrollerPadding(),
-                    mAuthController.isUdfpsEnrolled());
+                    mUpdateMonitor.isUdfpsEnrolled());
             mClockPositionAlgorithm.run(mClockPositionResult);
             mKeyguardStatusViewController.updatePosition(
                     mClockPositionResult.clockX, mClockPositionResult.clockY, animateClock);
@@ -914,7 +914,7 @@
                         - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
                         - mKeyguardStatusViewController.getLogoutButtonHeight();
 
-        if (mAuthController.isUdfpsEnrolled()) {
+        if (mUpdateMonitor.isUdfpsEnrolled()) {
             availableSpace = mNotificationStackScrollLayoutController.getHeight()
                     - minPadding - shelfSize
                     - (mStatusBar.getDisplayHeight() - mAuthController.getUdfpsRegion().top);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index af2f3e5..a930a89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -22,13 +22,13 @@
 import android.view.WindowManager;
 
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.util.ArrayList;
 import java.util.Optional;
@@ -51,7 +51,7 @@
     private final int mDisplayId;
     protected final Lazy<StatusBar> mStatusBarLazy;
     private final Lazy<AssistManager> mAssistManagerLazy;
-    private final Optional<Lazy<Bubbles>> mBubblesOptional;
+    private final Optional<Bubbles> mBubblesOptional;
 
     private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
@@ -64,7 +64,7 @@
             WindowManager windowManager,
             Lazy<StatusBar> statusBarLazy,
             Lazy<AssistManager> assistManagerLazy,
-            Optional<Lazy<Bubbles>> bubblesOptional
+            Optional<Bubbles> bubblesOptional
     ) {
         mCommandQueue = commandQueue;
         mStatusBarStateController = statusBarStateController;
@@ -135,7 +135,7 @@
             getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
             getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
         } else if (mBubblesOptional.isPresent()) {
-            mBubblesOptional.get().get().collapseStack();
+            mBubblesOptional.get().collapseStack();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a8d4104..5c7f54b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -144,7 +144,6 @@
 import com.android.systemui.SystemUI;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.charging.WirelessChargingAnimation;
 import com.android.systemui.classifier.FalsingLog;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -229,6 +228,7 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.splitscreen.SplitScreen;
 
 import java.io.FileDescriptor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index b69da85..13d5bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -31,7 +31,6 @@
 import com.android.systemui.InitController;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -98,6 +97,7 @@
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.splitscreen.SplitScreen;
 
 import java.util.Optional;
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index e79d432..4b4e1df 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -26,7 +26,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.qs.QuickQSPanel;
-import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 
 import java.lang.reflect.InvocationTargetException;
@@ -104,11 +103,6 @@
          * Creates the QuickQSPanel.
          */
         QuickQSPanel createQuickQSPanel();
-
-        /**
-         * Creates the QSCustomizer.
-         */
-        QSCustomizer createQSCustomizer();
     }
 
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index ad596c2..844f12e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -27,10 +27,10 @@
 import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
 import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
 
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
 import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 
 import android.app.INotificationManager;
 import android.app.Notification;
@@ -53,8 +53,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.BubbleEntry;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.model.SysUiState;
@@ -80,6 +78,8 @@
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.wm.shell.bubbles.BubbleEntry;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 91ae08e..bdca503 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -18,21 +18,27 @@
 
 import android.app.IActivityManager;
 import android.content.Context;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.view.IWindowManager;
+import android.view.WindowManager;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.bubbles.Bubbles;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.dagger.WMSingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.wm.shell.ShellDump;
 import com.android.wm.shell.ShellInit;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.AnimationThread;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.HandlerExecutor;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -116,6 +122,18 @@
 
     @WMSingleton
     @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
+    @WMSingleton
+    @Provides
+    static WindowManagerShellWrapper provideWindowManagerShellWrapper() {
+        return new WindowManagerShellWrapper();
+    }
+
+    @WMSingleton
+    @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
             IActivityManager activityManager,
             PipTouchHandler pipTouchHandler) {
@@ -166,8 +184,21 @@
     @BindsOptionalOf
     abstract SplitScreen optionalSplitScreen();
 
-    @BindsOptionalOf
-    abstract Bubbles optionalBubbles();
+    @WMSingleton
+    @Provides
+    static Optional<Bubbles> provideBubbles(Context context,
+            FloatingContentCoordinator floatingContentCoordinator,
+            IStatusBarService statusBarService,
+            WindowManager windowManager,
+            WindowManagerShellWrapper windowManagerShellWrapper,
+            LauncherApps launcherApps,
+            UiEventLogger uiEventLogger,
+            @Main Handler mainHandler,
+            ShellTaskOrganizer organizer) {
+        return Optional.of(BubbleController.create(context, null /* synchronizer */,
+                floatingContentCoordinator, statusBarService, windowManager,
+                windowManagerShellWrapper, launcherApps, uiEventLogger, mainHandler, organizer));
+    }
 
     @WMSingleton
     @Provides
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index e5847b0..f1c687f 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -63,7 +63,7 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name="com.android.systemui.bubbles.BubblesTestActivity"
+        <activity android:name=".wmshell.BubblesTestActivity"
             android:allowEmbedded="true"
             android:documentLaunchMode="always"
             android:excludeFromRecents="true"
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index 594f0b1..cbd6e86 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -116,6 +116,13 @@
         filter.add(s -> s.startsWith("com.android.systemui")
                 || s.startsWith("com.android.keyguard"));
 
+        // Screenshots run in an isolated process and should not be run
+        // with the main process dependency graph because it will not exist
+        // at runtime and could lead to incorrect tests which assume
+        // the main SystemUI process. Therefore, exclude this package
+        // from the base class whitelist.
+        filter.add(s -> !s.startsWith("com.android.systemui.screenshot"));
+
         try {
             return scanner.getClassPathEntries(filter);
         } catch (IOException e) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index caab2ab..d78090a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -201,7 +201,7 @@
         when(mTelephonyManager.getServiceStateForSubscriber(anyInt()))
                 .thenReturn(new ServiceState());
         when(mLockPatternUtils.getLockSettings()).thenReturn(mLockSettings);
-        when(mAuthController.isUdfpsEnrolled()).thenReturn(false);
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
         mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
         mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
         mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
index 3494bd6..fa29fd4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java
@@ -49,7 +49,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.recents.OverviewProxyService;
 
 import org.junit.Before;
@@ -66,7 +65,6 @@
 
     private KeyButtonView mKeyButtonView;
     private MetricsLogger mMetricsLogger;
-    private Bubbles mBubbles;
     private UiEventLogger mUiEventLogger;
     private InputManager mInputManager = mock(InputManager.class);
     @Captor
@@ -76,7 +74,6 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
         mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
-        mBubbles = mDependency.injectMockDependency(Bubbles.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
         mUiEventLogger = mDependency.injectMockDependency(UiEventLogger.class);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
index 8039192..c050b62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java
@@ -52,7 +52,7 @@
 
     private MetricsLogger mMetricsLogger;
     private QSDetail mQsDetail;
-    private QSPanel mQsPanel;
+    private QSPanelController mQsPanelController;
     private QuickStatusBarHeader mQuickHeader;
     private ActivityStarter mActivityStarter;
     private DetailAdapter mMockDetailAdapter;
@@ -68,9 +68,9 @@
             mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
             mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
             mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
-            mQsPanel = mock(QSPanel.class);
+            mQsPanelController = mock(QSPanelController.class);
             mQuickHeader = mock(QuickStatusBarHeader.class);
-            mQsDetail.setQsPanel(mQsPanel, mQuickHeader, mock(QSFooter.class));
+            mQsDetail.setQsPanel(mQsPanelController, mQuickHeader, mock(QSFooter.class));
 
             mMockDetailAdapter = mock(DetailAdapter.class);
             when(mMockDetailAdapter.createDetailView(any(), any(), any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index bf0e084..64ef6dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -38,6 +38,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 
 import org.junit.Before;
@@ -61,6 +62,12 @@
     @Mock
     private QSTileHost mQSTileHost;
     @Mock
+    private QSCustomizerController mQSCustomizerController;
+    @Mock
+    private QSTileRevealController.Factory mQSTileRevealControllerFactory;
+    @Mock
+    private QSTileRevealController mQSTileRevealController;
+    @Mock
     private MediaHost mMediaHost;
     @Mock
     private MetricsLogger mMetricsLogger;
@@ -70,15 +77,19 @@
     QSTileImpl mQSTile;
     @Mock
     QSTileView mQSTileView;
+    @Mock
+    PagedTileLayout mPagedTileLayout;
 
     private QSPanelControllerBase<QSPanel> mController;
 
     /** Implementation needed to ensure we have a reflectively-available class name. */
     private static class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
         protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
-                MetricsLogger metricsLogger,
-                UiEventLogger uiEventLogger, DumpManager dumpManager) {
-            super(view, host, metricsLogger, uiEventLogger, dumpManager);
+                QSCustomizerController qsCustomizerController,
+                QSTileRevealController.Factory qsTileRevealControllerFactory,
+                MetricsLogger metricsLogger, UiEventLogger uiEventLogger, DumpManager dumpManager) {
+            super(view, host, qsCustomizerController, qsTileRevealControllerFactory, metricsLogger,
+                    uiEventLogger, dumpManager);
         }
     }
 
@@ -91,11 +102,14 @@
         when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
         when(mQSPanel.openPanelEvent()).thenReturn(QSEvent.QS_PANEL_EXPANDED);
         when(mQSPanel.closePanelEvent()).thenReturn(QSEvent.QS_PANEL_COLLAPSED);
+        when(mQSPanel.createRegularTileLayout()).thenReturn(mPagedTileLayout);
         when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
         when(mQSTileHost.createTileView(eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
+        when(mQSTileRevealControllerFactory.create(any())).thenReturn(mQSTileRevealController);
 
         mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
-                mMetricsLogger, mUiEventLogger, mDumpManager);
+                mQSCustomizerController, mQSTileRevealControllerFactory, mMetricsLogger,
+                mUiEventLogger, mDumpManager);
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index 0ba0214..bce376a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.settings.BrightnessController;
 import com.android.systemui.settings.ToggleSlider;
@@ -58,6 +59,12 @@
     @Mock
     private QSTileHost mQSTileHost;
     @Mock
+    private QSCustomizerController mQSCustomizerController;
+    @Mock
+    private QSTileRevealController.Factory mQSTileRevealControllerFactory;
+    @Mock
+    private QSTileRevealController mQSTileRevealController;
+    @Mock
     private MediaHost mMediaHost;
     @Mock
     private MetricsLogger mMetricsLogger;
@@ -75,6 +82,9 @@
     QSTileImpl mQSTile;
     @Mock
     QSTileView mQSTileView;
+    @Mock
+    PagedTileLayout mPagedTileLayout;
+
 
     private QSPanelController mController;
 
@@ -85,14 +95,16 @@
         when(mQSPanel.getMediaHost()).thenReturn(mMediaHost);
         when(mQSPanel.isAttachedToWindow()).thenReturn(true);
         when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
+        when(mQSPanel.createRegularTileLayout()).thenReturn(mPagedTileLayout);
         when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
         when(mQSTileHost.createTileView(eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
         when(mBrightnessControllerFactory.create(any(ToggleSlider.class)))
                 .thenReturn(mBrightnessController);
+        when(mQSTileRevealControllerFactory.create(any())).thenReturn(mQSTileRevealController);
 
         mController = new QSPanelController(mQSPanel, mQSSecurityFooter, mTunerService,
-                mQSTileHost, mDumpManager, mMetricsLogger, mUiEventLogger,
-                mBrightnessControllerFactory);
+                mQSTileHost, mQSCustomizerController, mQSTileRevealControllerFactory, mDumpManager,
+                mMetricsLogger, mUiEventLogger, mBrightnessControllerFactory);
 
         mController.init();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index e38d54b..450ffac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -39,7 +39,6 @@
 import com.android.systemui.media.MediaHost;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.customize.QSCustomizer;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.SecurityController;
@@ -65,12 +64,8 @@
     @Mock
     private QSTileHost mHost;
     @Mock
-    private QSCustomizer mCustomizer;
-    @Mock
     private QSTileImpl dndTile;
     @Mock
-    private QSTileImpl mNonTile;
-    @Mock
     private QSPanelControllerBase.TileRecord mDndTileRecord;
     @Mock
     private QSLogger mQSLogger;
@@ -84,7 +79,6 @@
     @Mock
     private ActivityStarter mActivityStarter;
     private UiEventLoggerFake mUiEventLogger;
-    private String mCachedSpecs = "";
 
     @Before
     public void setup() throws Exception {
@@ -113,8 +107,6 @@
             when(dndTile.getTileSpec()).thenReturn("dnd");
             when(mHost.getTiles()).thenReturn(Collections.emptyList());
             when(mHost.createTileView(any(), anyBoolean())).thenReturn(mQSTileView);
-
-            mQsPanel.setCustomizer(mCustomizer);
             mQsPanel.addTile(mDndTileRecord);
             mQsPanel.setCallback(mCallback);
         });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index 204de929..3d53062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -15,7 +15,6 @@
 package com.android.systemui.qs.customize;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
 import android.testing.AndroidTestingRunner;
@@ -31,6 +30,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
 
@@ -40,17 +41,20 @@
 public class TileAdapterTest extends SysuiTestCase {
 
     private TileAdapter mTileAdapter;
+    @Mock
+    private QSTileHost mQSTileHost;
 
     @Before
     public void setup() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
         TestableLooper.get(this).runWithLooper(() -> mTileAdapter =
-                new TileAdapter(mContext, new UiEventLoggerFake()));
+                new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake()));
     }
 
     @Test
     public void testResetNotifiesHost() {
-        QSTileHost host = mock(QSTileHost.class);
-        mTileAdapter.resetTileSpecs(host, Collections.emptyList());
-        verify(host).changeTiles(any(), any());
+        mTileAdapter.resetTileSpecs(Collections.emptyList());
+        verify(mQSTileHost).changeTiles(any(), any());
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
new file mode 100644
index 0000000..a75c39c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/FakeScrollCaptureConnection.java
@@ -0,0 +1,142 @@
+/*
+ * 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.screenshot;
+
+import android.content.pm.ActivityInfo;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.HardwareRenderer;
+import android.graphics.Paint;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.os.RemoteException;
+import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureConnection;
+import android.view.Surface;
+
+/**
+ * An IScrollCaptureConnection which returns a sequence of solid filled rectangles in the
+ * locations requested, in alternating colors.
+ */
+class FakeScrollCaptureConnection extends IScrollCaptureConnection.Stub {
+    private final int[] mColors = {Color.RED, Color.GREEN, Color.BLUE};
+    private IScrollCaptureCallbacks mCallbacks;
+    private Surface mSurface;
+    private Paint mPaint;
+    private int mNextColor;
+    private HwuiContext mHwuiContext;
+
+    FakeScrollCaptureConnection(IScrollCaptureCallbacks cb) {
+        mCallbacks = cb;
+    }
+
+    @Override
+    public void startCapture(Surface surface) {
+        mSurface = surface;
+        mHwuiContext = new HwuiContext(false, surface);
+        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mPaint.setStyle(Paint.Style.FILL);
+        try {
+            mCallbacks.onCaptureStarted();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void requestImage(Rect rect) {
+        Canvas canvas = mHwuiContext.lockCanvas(rect.width(), rect.height());
+        mPaint.setColor(mColors[mNextColor]);
+        canvas.drawRect(rect, mPaint);
+        mNextColor = (mNextColor++) % mColors.length;
+        long frameNumber = mSurface.getNextFrameNumber();
+        mHwuiContext.unlockAndPost(canvas);
+        try {
+            mCallbacks.onCaptureBufferSent(frameNumber, rect);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void endCapture() {
+        try {
+            mCallbacks.onConnectionClosed();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        } finally {
+            mHwuiContext.destroy();
+            mSurface = null;
+            mCallbacks = null;
+        }
+    }
+
+    // From android.view.Surface, but issues render requests synchronously with waitForPresent(true)
+    private static final class HwuiContext {
+        private final RenderNode mRenderNode;
+        private final HardwareRenderer mHardwareRenderer;
+        private RecordingCanvas mCanvas;
+        private final boolean mIsWideColorGamut;
+
+        HwuiContext(boolean isWideColorGamut, Surface surface) {
+            mRenderNode = RenderNode.create("HwuiCanvas", null);
+            mRenderNode.setClipToBounds(false);
+            mRenderNode.setForceDarkAllowed(false);
+            mIsWideColorGamut = isWideColorGamut;
+
+            mHardwareRenderer = new HardwareRenderer();
+            mHardwareRenderer.setContentRoot(mRenderNode);
+            mHardwareRenderer.setSurface(surface, true);
+            mHardwareRenderer.setColorMode(
+                    isWideColorGamut
+                            ? ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT
+                            : ActivityInfo.COLOR_MODE_DEFAULT);
+            mHardwareRenderer.setLightSourceAlpha(0.0f, 0.0f);
+            mHardwareRenderer.setLightSourceGeometry(0.0f, 0.0f, 0.0f, 0.0f);
+        }
+
+        Canvas lockCanvas(int width, int height) {
+            if (mCanvas != null) {
+                throw new IllegalStateException("Surface was already locked!");
+            }
+            mCanvas = mRenderNode.beginRecording(width, height);
+            return mCanvas;
+        }
+
+        void unlockAndPost(Canvas canvas) {
+            if (canvas != mCanvas) {
+                throw new IllegalArgumentException("canvas object must be the same instance that "
+                        + "was previously returned by lockCanvas");
+            }
+            mRenderNode.endRecording();
+            mCanvas = null;
+            mHardwareRenderer.createRenderRequest()
+                    .setVsyncTime(System.nanoTime())
+                    .setWaitForPresent(true) // sync!
+                    .syncAndDraw();
+        }
+
+        void destroy() {
+            mHardwareRenderer.destroy();
+        }
+
+        boolean isWideColorGamut() {
+            return mIsWideColorGamut;
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
new file mode 100644
index 0000000..4aa730e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureClientTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.screenshot;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static java.util.Objects.requireNonNull;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.view.Display;
+import android.view.IScrollCaptureCallbacks;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
+import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
+import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.stubbing.Answer;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class ScrollCaptureClientTest extends SysuiTestCase {
+    private Context mContext;
+    private IWindowManager mWm;
+
+    @Spy private TestableConsumer<Session> mSessionConsumer;
+    @Spy private TestableConsumer<Connection> mConnectionConsumer;
+    @Spy private TestableConsumer<CaptureResult> mResultConsumer;
+    @Mock private Runnable mRunnable;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        DisplayManager displayManager = requireNonNull(
+                context.getSystemService(DisplayManager.class));
+        mContext = context.createDisplayContext(
+                displayManager.getDisplay(Display.DEFAULT_DISPLAY));
+        mWm = mock(IWindowManager.class);
+    }
+
+    @Test
+    public void testBasicClientFlow() throws RemoteException {
+        doAnswer((Answer<Void>) invocation -> {
+            IScrollCaptureCallbacks cb = invocation.getArgument(3);
+            cb.onConnected(
+                    new FakeScrollCaptureConnection(cb),
+                    /* scrollBounds */ new Rect(0, 0, 100, 100),
+                    /* positionInWindow */ new Point(0, 0));
+            return null;
+        }).when(mWm).requestScrollCapture(/* displayId */ anyInt(), /* token */  isNull(),
+                /* taskId */ anyInt(), any(IScrollCaptureCallbacks.class));
+
+        // Create client
+        ScrollCaptureClient client = new ScrollCaptureClient(mContext, mWm);
+
+        client.request(Display.DEFAULT_DISPLAY, mConnectionConsumer);
+        verify(mConnectionConsumer, timeout(100)).accept(any(Connection.class));
+
+        Connection conn = mConnectionConsumer.getValue();
+
+        conn.start(5, mSessionConsumer);
+        verify(mSessionConsumer, timeout(100)).accept(any(Session.class));
+
+        Session session = mSessionConsumer.getValue();
+        Rect request = new Rect(0, 0, session.getMaxTileWidth(), session.getMaxTileHeight());
+
+        session.requestTile(request, mResultConsumer);
+        verify(mResultConsumer, timeout(100)).accept(any(CaptureResult.class));
+
+        CaptureResult result = mResultConsumer.getValue();
+        assertThat(result.requested).isEqualTo(request);
+        assertThat(result.captured).isEqualTo(result.requested);
+        assertThat(result.image).isNotNull();
+
+        session.end(mRunnable);
+        verify(mRunnable, timeout(100)).run();
+
+        // TODO verify image
+        // TODO test threading
+        // TODO test failures
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TestableConsumer.java
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
copy to packages/SystemUI/tests/src/com/android/systemui/screenshot/TestableConsumer.java
index 24768cd..a554e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TestableConsumer.java
@@ -13,17 +13,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.bubbles.storage
 
-import android.annotation.DimenRes
-import android.annotation.UserIdInt
+package com.android.systemui.screenshot;
 
-data class BubbleEntity(
-    @UserIdInt val userId: Int,
-    val packageName: String,
-    val shortcutId: String,
-    val key: String,
-    val desiredHeight: Int,
-    @DimenRes val desiredHeightResId: Int,
-    val title: String? = null
-)
+import java.util.function.Consumer;
+
+/** Accepts and retains the most recent value for verification */
+class TestableConsumer<T> implements Consumer<T> {
+    T mValue;
+
+    @Override
+    public void accept(T t) {
+        mValue = t;
+    }
+
+    public T getValue() {
+        return mValue;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 10eca00..7fb7b86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -36,7 +36,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.DynamicChildBindController;
@@ -54,6 +53,7 @@
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import com.google.android.collect.Lists;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index cd46dda..05cf33a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,7 +42,6 @@
 
 import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -54,6 +53,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import org.junit.After;
 import org.junit.Before;
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 4698b8e..f7dfe0b 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
@@ -76,12 +76,12 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.statusbar.SbnBuilder;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.wmshell.BubblesManager;
+import com.android.systemui.wmshell.BubblesTestActivity;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 48375e0..baae8fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -45,8 +45,6 @@
 
 import com.android.systemui.R;
 import com.android.systemui.TestableDependency;
-import com.android.systemui.bubbles.Bubbles;
-import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.media.MediaFeatureFlag;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.plugins.FalsingManager;
@@ -71,6 +69,8 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.policy.InflatedSmartReplies;
 import com.android.systemui.wmshell.BubblesManager;
+import com.android.systemui.wmshell.BubblesTestActivity;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import org.mockito.ArgumentCaptor;
 
@@ -432,7 +432,8 @@
                 mStatusBarStateController,
                 mPeopleNotificationIdentifier,
                 mock(OnUserInteractionCallback.class),
-                Optional.of(mock(BubblesManager.class)));
+                Optional.of(mock(BubblesManager.class)),
+                mock(NotificationGutsManager.class));
 
         row.setAboveShelfChangedListener(aboveShelf -> { });
         mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
index 72a0258..aca3424 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenIconControllerTest.java
@@ -32,7 +32,6 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -81,25 +80,21 @@
     private Resources mResources;
     @Mock
     private HeadsUpManagerPhone mHeadsUpManagerPhone;
-    @Mock
-    private AuthController mAuthController;
 
     private LockscreenLockIconController mLockIconController;
     private OnAttachStateChangeListener mOnAttachStateChangeListener;
 
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mAuthController.isUdfpsEnrolled()).thenReturn(false);
         when(mLockIcon.getContext()).thenReturn(mContext);
         mLockIconController = new LockscreenLockIconController(
                 mLockscreenGestureLogger, mKeyguardUpdateMonitor, mLockPatternUtils,
                 mShadeController, mAccessibilityController, mKeyguardIndicationController,
                 mStatusBarStateController, mConfigurationController, mNotificationWakeUpCoordinator,
                 mKeyguardBypassController, mDockManager, mKeyguardStateController, mResources,
-                mHeadsUpManagerPhone, mAuthController);
+                mHeadsUpManagerPhone);
 
         ArgumentCaptor<OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
                 ArgumentCaptor.forClass(OnAttachStateChangeListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index b0086ef..7c8b413 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -37,7 +37,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -48,6 +47,7 @@
 import com.android.systemui.statusbar.notification.row.RowContentBindParams;
 import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
index f81672a..3e9fd51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerLegacyTest.java
@@ -30,12 +30,12 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
index a1d419c..858227f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconAreaControllerTest.java
@@ -27,7 +27,6 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -35,6 +34,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.wm.shell.bubbles.Bubbles;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 3b123f6..e1f8596 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
@@ -204,7 +205,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        when(mAuthController.isUdfpsEnrolled()).thenReturn(false);
+        when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
         when(mHeadsUpCallback.getContext()).thenReturn(mContext);
         when(mView.getResources()).thenReturn(mResources);
         when(mResources.getConfiguration()).thenReturn(mConfiguration);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 9f096da..2f9b601 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -83,7 +83,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.demomode.DemoModeController;
@@ -146,6 +145,7 @@
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.splitscreen.SplitScreen;
 
 import org.junit.Before;
@@ -337,7 +337,7 @@
         mShadeController = new ShadeControllerImpl(mCommandQueue,
                 mStatusBarStateController, mNotificationShadeWindowController,
                 mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
-                () -> mStatusBar, () -> mAssistManager, Optional.of(() -> mBubbles));
+                () -> mStatusBar, () -> mAssistManager, Optional.of(mBubbles));
 
         mStatusBar = new StatusBar(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 88d0401..a46e563 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -64,14 +64,6 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.Bubble;
-import com.android.systemui.bubbles.BubbleData;
-import com.android.systemui.bubbles.BubbleDataRepository;
-import com.android.systemui.bubbles.BubbleEntry;
-import com.android.systemui.bubbles.BubbleLogger;
-import com.android.systemui.bubbles.BubblePositioner;
-import com.android.systemui.bubbles.BubbleStackView;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -102,6 +94,14 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.bubbles.Bubble;
+import com.android.wm.shell.bubbles.BubbleData;
+import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEntry;
+import com.android.wm.shell.bubbles.BubbleLogger;
+import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
 import com.google.common.collect.ImmutableList;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
rename to packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.java
index 43d2ad1..f4d96a12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubblesTestActivity.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTestActivity.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.bubbles;
+package com.android.systemui.wmshell;
 
 import android.app.Activity;
 import android.content.Intent;
@@ -27,8 +27,7 @@
  */
 public class BubblesTestActivity extends Activity {
 
-    public static final String BUBBLE_ACTIVITY_OPENED =
-            "com.android.systemui.bubbles.BUBBLE_ACTIVITY_OPENED";
+    public static final String BUBBLE_ACTIVITY_OPENED = "BUBBLE_ACTIVITY_OPENED";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
index 99c8ca4..d8033db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
@@ -60,13 +60,6 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleData;
-import com.android.systemui.bubbles.BubbleDataRepository;
-import com.android.systemui.bubbles.BubbleEntry;
-import com.android.systemui.bubbles.BubbleLogger;
-import com.android.systemui.bubbles.BubblePositioner;
-import com.android.systemui.bubbles.BubbleStackView;
-import com.android.systemui.bubbles.Bubbles;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -95,6 +88,13 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.bubbles.BubbleData;
+import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEntry;
+import com.android.wm.shell.bubbles.BubbleLogger;
+import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.bubbles.BubbleStackView;
+import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 2273bc4..fd39b6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -22,13 +22,13 @@
 import android.view.WindowManager;
 
 import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.BubbleData;
-import com.android.systemui.bubbles.BubbleDataRepository;
-import com.android.systemui.bubbles.BubbleLogger;
-import com.android.systemui.bubbles.BubblePositioner;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
+import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleLogger;
+import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 
 /**
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index ad85784..a1ad72c 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -84,7 +84,6 @@
 import com.android.internal.util.SyncResultReceiver;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
-import com.android.server.SystemService.TargetUser;
 import com.android.server.autofill.ui.AutoFillUI;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -170,7 +169,7 @@
                 // beneath it is brought back to top. Ideally, we should just hide the UI and
                 // bring it back when the activity resumes.
                 synchronized (mLock) {
-                    visitServicesLocked((s) -> s.destroyFinishedSessionsLocked());
+                    visitServicesLocked((s) -> s.forceRemoveFinishedSessionsLocked());
                 }
                 mUi.hideAll(null);
             }
@@ -386,18 +385,18 @@
     }
 
     // Called by Shell command.
-    void destroySessions(@UserIdInt int userId, IResultReceiver receiver) {
-        Slog.i(TAG, "destroySessions() for userId " + userId);
+    void removeAllSessions(@UserIdInt int userId, IResultReceiver receiver) {
+        Slog.i(TAG, "removeAllSessions() for userId " + userId);
         enforceCallingPermissionForManagement();
 
         synchronized (mLock) {
             if (userId != UserHandle.USER_ALL) {
                 AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    service.destroySessionsLocked();
+                    service.forceRemoveAllSessionsLocked();
                 }
             } else {
-                visitServicesLocked((s) -> s.destroySessionsLocked());
+                visitServicesLocked((s) -> s.forceRemoveAllSessionsLocked());
             }
         }
 
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 864ead1..3212698 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -215,14 +215,14 @@
     @GuardedBy("mLock")
     @Override // from PerUserSystemService
     protected boolean updateLocked(boolean disabled) {
-        destroySessionsLocked();
+        forceRemoveAllSessionsLocked();
         final boolean enabledChanged = super.updateLocked(disabled);
         if (enabledChanged) {
             if (!isEnabledLocked()) {
                 final int sessionCount = mSessions.size();
                 for (int i = sessionCount - 1; i >= 0; i--) {
                     final Session session = mSessions.valueAt(i);
-                    session.removeSelfLocked();
+                    session.removeFromServiceLocked();
                 }
             }
             sendStateToClients(/* resetClient= */ false);
@@ -442,7 +442,7 @@
         if (sVerbose) Slog.v(TAG, "finishSessionLocked(): session finished on save? " + finished);
 
         if (finished) {
-            session.removeSelfLocked();
+            session.removeFromServiceLocked();
         }
     }
 
@@ -457,7 +457,7 @@
             Slog.w(TAG, "cancelSessionLocked(): no session for " + sessionId + "(" + uid + ")");
             return;
         }
-        session.removeSelfLocked();
+        session.removeFromServiceLocked();
     }
 
     @GuardedBy("mLock")
@@ -483,7 +483,7 @@
                         componentName.getPackageName());
                 Settings.Secure.putStringForUser(getContext().getContentResolver(),
                         Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
-                destroySessionsLocked();
+                forceRemoveAllSessionsLocked();
             } else {
                 Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
                         + serviceInfo + ") does not match Settings (" + autoFillService + ")");
@@ -1107,35 +1107,41 @@
     }
 
     @GuardedBy("mLock")
-    void destroySessionsLocked() {
-        if (mSessions.size() == 0) {
+    void forceRemoveAllSessionsLocked() {
+        final int sessionCount = mSessions.size();
+        if (sessionCount == 0) {
             mUi.destroyAll(null, null, false);
             return;
         }
-        while (mSessions.size() > 0) {
-            mSessions.valueAt(0).forceRemoveSelfLocked();
+
+        for (int i = sessionCount - 1; i >= 0; i--) {
+            mSessions.valueAt(i).forceRemoveFromServiceLocked();
         }
     }
 
     @GuardedBy("mLock")
-    void destroySessionsForAugmentedAutofillOnlyLocked() {
+    void forceRemoveForAugmentedOnlySessionsLocked() {
         final int sessionCount = mSessions.size();
         for (int i = sessionCount - 1; i >= 0; i--) {
-            mSessions.valueAt(i).forceRemoveSelfIfForAugmentedAutofillOnlyLocked();
+            mSessions.valueAt(i).forceRemoveFromServiceIfForAugmentedOnlyLocked();
         }
     }
 
+    /**
+     * This method is called exclusively in response to {@code Intent.ACTION_CLOSE_SYSTEM_DIALOGS}.
+     * The method removes all sessions that are finished but showing SaveUI due to how SaveUI is
+     * managed (see b/64940307). Otherwise it will remove any augmented autofill generated windows.
+     */
     // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
     @GuardedBy("mLock")
-    void destroyFinishedSessionsLocked() {
+    void forceRemoveFinishedSessionsLocked() {
         final int sessionCount = mSessions.size();
         for (int i = sessionCount - 1; i >= 0; i--) {
             final Session session = mSessions.valueAt(i);
             if (session.isSavingLocked()) {
                 if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
-                session.forceRemoveSelfLocked();
-            }
-            else {
+                session.forceRemoveFromServiceLocked();
+            } else {
                 session.destroyAugmentedAutofillWindowsLocked();
             }
         }
@@ -1261,7 +1267,7 @@
                     Slog.v(TAG, "updateRemoteAugmentedAutofillService(): "
                             + "destroying old remote service");
                 }
-                destroySessionsForAugmentedAutofillOnlyLocked();
+                forceRemoveForAugmentedOnlySessionsLocked();
                 mRemoteAugmentedAutofillService.unbind();
                 mRemoteAugmentedAutofillService = null;
                 mRemoteAugmentedAutofillServiceInfo = null;
@@ -1663,7 +1669,7 @@
                                 Slog.i(TAG, "Prune session " + sessionToRemove.id + " ("
                                     + sessionToRemove.getActivityTokenLocked() + ")");
                             }
-                            sessionToRemove.removeSelfLocked();
+                            sessionToRemove.removeFromServiceLocked();
                         }
                     }
                 }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index bbe37a5..68e6290 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -355,7 +355,7 @@
                 latch.countDown();
             }
         };
-        return requestSessionCommon(pw, latch, () -> mService.destroySessions(userId, receiver));
+        return requestSessionCommon(pw, latch, () -> mService.removeAllSessions(userId, receiver));
     }
 
     private int requestList(PrintWriter pw) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0302b22..b48d71a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -227,8 +227,8 @@
     private boolean mHasCallback;
 
     /**
-     * Extras sent by service on {@code onFillRequest()} calls; the first non-null extra is saved
-     * and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
+     * Extras sent by service on {@code onFillRequest()} calls; the most recent non-null extra is
+     * saved and used on subsequent {@code onFillRequest()} and {@code onSaveRequest()} calls.
      */
     @GuardedBy("mLock")
     private Bundle mClientState;
@@ -1086,7 +1086,7 @@
         if (showMessage) {
             getUiForShowing().showError(message, this);
         }
-        removeSelf();
+        removeFromService();
     }
 
     // FillServiceCallbacks
@@ -1111,7 +1111,7 @@
         }
 
         // Nothing left to do...
-        removeSelf();
+        removeFromService();
     }
 
     // FillServiceCallbacks
@@ -1147,7 +1147,7 @@
         if (showMessage) {
             getUiForShowing().showError(message, this);
         }
-        removeSelf();
+        removeFromService();
     }
 
     /**
@@ -1179,7 +1179,7 @@
     @Override
     public void onServiceDied(@NonNull RemoteFillService service) {
         Slog.w(TAG, "removing session because service died");
-        forceRemoveSelfLocked();
+        forceRemoveFromServiceLocked();
     }
 
     // AutoFillUiCallback
@@ -1199,7 +1199,7 @@
             }
             fillInIntent = createAuthFillInIntentLocked(requestId, extras);
             if (fillInIntent == null) {
-                forceRemoveSelfLocked();
+                forceRemoveFromServiceLocked();
                 return;
             }
         }
@@ -1255,7 +1255,7 @@
             }
         }
         mHandler.sendMessage(obtainMessage(
-                Session::removeSelf, this));
+                Session::removeFromService, this));
     }
 
     // AutoFillUiCallback
@@ -1327,7 +1327,7 @@
     @Override
     public void cancelSession() {
         synchronized (mLock) {
-            removeSelfLocked();
+            removeFromServiceLocked();
         }
     }
 
@@ -1347,7 +1347,7 @@
                 return;
             }
             if (intent == null) {
-                removeSelfLocked();
+                removeFromServiceLocked();
             }
         }
         mHandler.sendMessage(obtainMessage(
@@ -1401,13 +1401,13 @@
             // Typically happens when app explicitly called cancel() while the service was showing
             // the auth UI.
             Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
-            removeSelf();
+            removeFromService();
             return;
         }
         final FillResponse authenticatedResponse = mResponses.get(requestId);
         if (authenticatedResponse == null || data == null) {
             Slog.w(TAG, "no authenticated response");
-            removeSelf();
+            removeFromService();
             return;
         }
 
@@ -1418,7 +1418,7 @@
             final Dataset dataset = authenticatedResponse.getDatasets().get(datasetIdx);
             if (dataset == null) {
                 Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response");
-                removeSelf();
+                removeFromService();
                 return;
             }
         }
@@ -1504,7 +1504,7 @@
                 Slog.d(TAG, "Rejecting empty/invalid auth result");
             }
             mService.resetLastAugmentedAutofillResponse();
-            removeSelfLocked();
+            removeFromServiceLocked();
             return;
         }
 
@@ -2715,7 +2715,7 @@
                         return;
                     }
                     if (sDebug) Slog.d(TAG, "Finishing session because URL bar changed");
-                    forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE);
+                    forceRemoveFromServiceLocked(AutofillManager.STATE_UNKNOWN_COMPAT_MODE);
                     return;
                 }
                 if (!Objects.equals(value, viewState.getCurrentValue())) {
@@ -3226,7 +3226,7 @@
             }
             // Nothing to be done, but need to notify client.
             notifyUnavailableToClient(AutofillManager.STATE_FINISHED, autofillableIds);
-            removeSelf();
+            removeFromService();
         } else {
             if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) {
                 if (sVerbose) {
@@ -3393,20 +3393,6 @@
     }
 
     @GuardedBy("mLock")
-    private void logAugmentedAutofillRequestLocked(int mode,
-            ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted,
-            Boolean isInline) {
-        final String historyItem =
-                "aug:id=" + id + " u=" + uid + " m=" + mode
-                        + " a=" + ComponentName.flattenToShortString(mComponentName)
-                        + " f=" + focusedId
-                        + " s=" + augmentedRemoteServiceName
-                        + " w=" + isWhitelisted
-                        + " i=" + isInline;
-        mService.getMaster().logRequestLocked(historyItem);
-    }
-
-    @GuardedBy("mLock")
     private void cancelAugmentedAutofillLocked() {
         final RemoteAugmentedAutofillService remoteService = mService
                 .getRemoteAugmentedAutofillServiceLocked();
@@ -3574,7 +3560,7 @@
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
             final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
             if (fillInIntent == null) {
-                forceRemoveSelfLocked();
+                forceRemoveFromServiceLocked();
                 return;
             }
             final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
@@ -3923,12 +3909,12 @@
     }
 
     /**
-     * Cleans up this session.
+     * Destroy this session and perform any clean up work.
      *
      * <p>Typically called in 2 scenarios:
      *
      * <ul>
-     *   <li>When the session naturally finishes (i.e., from {@link #removeSelfLocked()}.
+     *   <li>When the session naturally finishes (i.e., from {@link #removeFromServiceLocked()}.
      *   <li>When the service hosting the session is finished (for example, because the user
      *       disabled it).
      * </ul>
@@ -3990,32 +3976,32 @@
     }
 
     /**
-     * Cleans up this session and remove it from the service always, even if it does have a pending
+     * Destroy this session and remove it from the service always, even if it does have a pending
      * Save UI.
      */
     @GuardedBy("mLock")
-    void forceRemoveSelfLocked() {
-        forceRemoveSelfLocked(AutofillManager.STATE_UNKNOWN);
+    void forceRemoveFromServiceLocked() {
+        forceRemoveFromServiceLocked(AutofillManager.STATE_UNKNOWN);
     }
 
     @GuardedBy("mLock")
-    void forceRemoveSelfIfForAugmentedAutofillOnlyLocked() {
+    void forceRemoveFromServiceIfForAugmentedOnlyLocked() {
         if (sVerbose) {
-            Slog.v(TAG, "forceRemoveSelfIfForAugmentedAutofillOnly(" + this.id + "): "
+            Slog.v(TAG, "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + this.id + "): "
                     + mForAugmentedAutofillOnly);
         }
         if (!mForAugmentedAutofillOnly) return;
 
-        forceRemoveSelfLocked();
+        forceRemoveFromServiceLocked();
     }
 
     @GuardedBy("mLock")
-    void forceRemoveSelfLocked(int clientState) {
-        if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
+    void forceRemoveFromServiceLocked(int clientState) {
+        if (sVerbose) Slog.v(TAG, "forceRemoveFromServiceLocked(): " + mPendingSaveUi);
 
         final boolean isPendingSaveUi = isSaveUiPendingLocked();
         mPendingSaveUi = null;
-        removeSelfLocked();
+        removeFromServiceLocked();
         mUi.destroyAll(mPendingSaveUi, this, false);
         if (!isPendingSaveUi) {
             try {
@@ -4036,28 +4022,28 @@
     }
 
     /**
-     * Thread-safe version of {@link #removeSelfLocked()}.
+     * Thread-safe version of {@link #removeFromServiceLocked()}.
      */
-    private void removeSelf() {
+    private void removeFromService() {
         synchronized (mLock) {
-            removeSelfLocked();
+            removeFromServiceLocked();
         }
     }
 
     /**
-     * Cleans up this session and remove it from the service, but but only if it does not have a
+     * Destroy this session and remove it from the service, but but only if it does not have a
      * pending Save UI.
      */
     @GuardedBy("mLock")
-    void removeSelfLocked() {
-        if (sVerbose) Slog.v(TAG, "removeSelfLocked(" + this.id + "): " + mPendingSaveUi);
+    void removeFromServiceLocked() {
+        if (sVerbose) Slog.v(TAG, "removeFromServiceLocked(" + this.id + "): " + mPendingSaveUi);
         if (mDestroyed) {
-            Slog.w(TAG, "Call to Session#removeSelfLocked() rejected - session: "
+            Slog.w(TAG, "Call to Session#removeFromServiceLocked() rejected - session: "
                     + id + " destroyed");
             return;
         }
         if (isSaveUiPendingLocked()) {
-            Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
+            Slog.i(TAG, "removeFromServiceLocked() ignored, waiting for pending save ui");
             return;
         }
 
@@ -4139,6 +4125,20 @@
         requestLog.addTaggedData(tag, value);
     }
 
+    @GuardedBy("mLock")
+    private void logAugmentedAutofillRequestLocked(int mode,
+            ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted,
+            Boolean isInline) {
+        final String historyItem =
+                "aug:id=" + id + " u=" + uid + " m=" + mode
+                        + " a=" + ComponentName.flattenToShortString(mComponentName)
+                        + " f=" + focusedId
+                        + " s=" + augmentedRemoteServiceName
+                        + " w=" + isWhitelisted
+                        + " i=" + isInline;
+        mService.getMaster().logRequestLocked(historyItem);
+    }
+
     private void wtf(@Nullable Exception e, String fmt, Object...args) {
         final String message = String.format(fmt, args);
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 9fc8f0b..ef6dab5 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -56,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -100,6 +101,10 @@
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
     private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
             "persist.device_config.configuration.disable_rescue_party";
+    private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
+            "persist.device_config.configuration.disable_rescue_party_factory_reset";
+    // The DeviceConfig namespace containing all RescueParty switches.
+    private static final String NAMESPACE_CONFIGURATION = "configuration";
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -215,6 +220,10 @@
         if (SettingsToPropertiesMapper.isNativeFlagsResetPerformed()) {
             String[] resetNativeCategories = SettingsToPropertiesMapper.getResetNativeCategories();
             for (int i = 0; i < resetNativeCategories.length; i++) {
+                // Don't let RescueParty reset the namespace for RescueParty switches.
+                if (NAMESPACE_CONFIGURATION.equals(resetNativeCategories[i])) {
+                    continue;
+                }
                 DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
                         resetNativeCategories[i]);
             }
@@ -225,8 +234,10 @@
      * Get the next rescue level. This indicates the next level of mitigation that may be taken.
      */
     private static int getNextRescueLevel() {
+        int maxRescueLevel = SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)
+                ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET;
         return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
-                LEVEL_NONE, LEVEL_FACTORY_RESET);
+                LEVEL_NONE, maxRescueLevel);
     }
 
     /**
@@ -349,12 +360,30 @@
     private static void resetDeviceConfig(Context context, int resetMode,
             @Nullable String failedPackage) {
         if (!shouldPerformScopedResets() || failedPackage == null) {
-            DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null);
+            resetAllAffectedNamespaces(context, resetMode);
         } else {
             performScopedReset(context, resetMode, failedPackage);
         }
     }
 
+    private static void resetAllAffectedNamespaces(Context context, int resetMode) {
+        RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
+        Set<String> allAffectedNamespaces = rescuePartyObserver.getAllAffectedNamespaceSet();
+
+        Slog.w(TAG,
+                "Performing reset for all affected namespaces: "
+                        + Arrays.toString(allAffectedNamespaces.toArray()));
+        Iterator<String> it = allAffectedNamespaces.iterator();
+        while (it.hasNext()) {
+            String namespace = it.next();
+            // Don't let RescueParty reset the namespace for RescueParty switches.
+            if (NAMESPACE_CONFIGURATION.equals(namespace)) {
+                continue;
+            }
+            DeviceConfig.resetToDefaults(resetMode, namespace);
+        }
+    }
+
     private static boolean shouldPerformScopedResets() {
         int rescueLevel = MathUtils.constrain(
                 SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE),
@@ -367,16 +396,21 @@
         RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstance(context);
         Set<String> affectedNamespaces = rescuePartyObserver.getAffectedNamespaceSet(
                 failedPackage);
-        if (affectedNamespaces == null) {
-            DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null);
-        } else {
+        // If we can't find namespaces affected for current package,
+        // skip this round of reset.
+        if (affectedNamespaces != null) {
             Slog.w(TAG,
                     "Performing scoped reset for package: " + failedPackage
                             + ", affected namespaces: "
                             + Arrays.toString(affectedNamespaces.toArray()));
             Iterator<String> it = affectedNamespaces.iterator();
             while (it.hasNext()) {
-                DeviceConfig.resetToDefaults(resetMode, it.next());
+                String namespace = it.next();
+                // Don't let RescueParty reset the namespace for RescueParty switches.
+                if (NAMESPACE_CONFIGURATION.equals(namespace)) {
+                    continue;
+                }
+                DeviceConfig.resetToDefaults(resetMode, namespace);
             }
         }
     }
@@ -514,6 +548,10 @@
             return mCallingPackageNamespaceSetMap.get(failedPackage);
         }
 
+        private synchronized Set<String> getAllAffectedNamespaceSet() {
+            return new HashSet<String>(mNamespaceCallingPackageSetMap.keySet());
+        }
+
         private synchronized Set<String> getCallingPackagesSet(String namespace) {
             return mNamespaceCallingPackageSetMap.get(namespace);
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0814937..9f2216d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7745,11 +7745,15 @@
      * @return true if the process should exit immediately (WTF is fatal)
      */
     @Override
-    public boolean handleApplicationWtf(final IBinder app, final String tag, boolean system,
-            final ApplicationErrorReport.ParcelableCrashInfo crashInfo, int immediateCallerPid) {
+    public boolean handleApplicationWtf(@Nullable final IBinder app, @Nullable final String tag,
+            boolean system, @NonNull final ApplicationErrorReport.ParcelableCrashInfo crashInfo,
+            int immediateCallerPid) {
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
 
+        // Internal callers in RuntimeInit should always generate a crashInfo.
+        Preconditions.checkNotNull(crashInfo);
+
         // If this is coming from the system, we could very well have low-level
         // system locks held, so we want to do this all asynchronously.  And we
         // never want this to become fatal, so there is that too.
@@ -7782,14 +7786,15 @@
         }
     }
 
-    ProcessRecord handleApplicationWtfInner(int callingUid, int callingPid, IBinder app, String tag,
-            final ApplicationErrorReport.CrashInfo crashInfo) {
+    ProcessRecord handleApplicationWtfInner(int callingUid, int callingPid, @Nullable IBinder app,
+            @Nullable String tag, @Nullable final ApplicationErrorReport.CrashInfo crashInfo) {
         final ProcessRecord r = findAppProcess(app, "WTF");
         final String processName = app == null ? "system_server"
                 : (r == null ? "unknown" : r.processName);
 
         EventLogTags.writeAmWtf(UserHandle.getUserId(callingUid), callingPid,
-                processName, r == null ? -1 : r.info.flags, tag, crashInfo.exceptionMessage);
+                processName, r == null ? -1 : r.info.flags, tag,
+                crashInfo == null ? "unknown" : crashInfo.exceptionMessage);
 
         FrameworkStatsLog.write(FrameworkStatsLog.WTF_OCCURRED, callingUid, tag, processName,
                 callingPid, (r != null) ? r.getProcessClassEnum() : 0);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 6a4ca8d..0b2d4d7 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -151,6 +151,8 @@
  * run at a later time. Similarly, when a sync succeeds, backoff is cleared and all associated syncs
  * are rescheduled. A rescheduled sync will get a new jobId.
  *
+ * See also {@code SyncManager.md} in the same directory for how app-standby affects sync adapters.
+ *
  * @hide
  */
 public class SyncManager {
diff --git a/services/core/java/com/android/server/content/SyncManager.md b/services/core/java/com/android/server/content/SyncManager.md
new file mode 100644
index 0000000..8507abd
--- /dev/null
+++ b/services/core/java/com/android/server/content/SyncManager.md
@@ -0,0 +1,122 @@
+# Sync Manager notes
+
+## App-standby and Sync Manager
+
+Android 9 Pie introduced
+["App Standby Buckets"](https://developer.android.com/topic/performance/appstandby), which throttles various things
+including
+[JobScheduler](https://developer.android.com/reference/android/app/job/JobScheduler)
+and [AlarmManager](https://developer.android.com/reference/android/app/AlarmManager),
+[among other things](https://developer.android.com/topic/performance/power/power-details),
+for background applications.
+
+Because SyncManager executes sync operations as JobScheduler jobs, sync operations are subject
+to the same throttling.
+
+However, unlike JobScheduler jobs, any apps (with the proper permission) can schedule a sync
+operation in any other apps using
+[ContentResolver.requestSync()](https://developer.android.com/reference/android/content/ContentResolver#requestSync(android.content.SyncRequest)),
+whch means it's possible for a foreground app to request a sync in another app that is either in the
+background or is not even running.
+For example, when the user hits the refresh button on the Contacts app, it'll
+request sync to all the contacts sync adapters, which are implemented in other packages (and they're
+likely not in the foreground).
+
+Because of this, calls to
+[ContentResolver.requestSync()](https://developer.android.com/reference/android/content/ContentResolver#requestSync(android.content.SyncRequest))
+made by foreground apps are special cased such that the resulting sync operations will be
+exempted from app-standby throttling.
+
+### Two Levels of Exemption
+Specifically, there are two different levels of exemption, depending on the state of the caller:
+1. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET`
+2. `ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP`, which is more powerful than 1.
+
+The exemption level is calculated in
+[ContentService.getSyncExemptionAndCleanUpExtrasForCaller()](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/content/ContentService.java?q=%22int%20getSyncExemptionAndCleanUpExtrasForCaller%22&ss=android%2Fplatform%2Fsuperproject),
+which was [implemented slightly differently](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/content/ContentService.java?q=%22int%20getSyncExemptionAndCleanUpExtrasForCaller%22&ss=android%2Fplatform%2Fsuperproject)
+in Android 9, compared to Android 10 and later.
+
+The logic is as follows:
+- When the caller's procstate is `PROCESS_STATE_TOP` or above,
+  meaning if the caller has a foreground activity,
+  `SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP` will be set.
+
+- Otherwise, when the caller's procstate is `PROCESS_STATE_IMPORTANT_FOREGROUND` or above,
+  e.g. when the caller has a foreground service, a service bound by the system of a specific kind,
+  `SYNC_EXEMPTION_PROMOTE_BUCKET` will be set.
+
+- Additionally, on Android 10 and later, when the caller is
+  "UID-active" (but the procstate is below `PROCESS_STATE_TOP`),
+  `SYNC_EXEMPTION_PROMOTE_BUCKET` will be set.
+  This is what happens when the app has just received a high-priority FCM, for example.
+  Temp-allowlist is also used in various other situations.
+
+### Behavior of Each Exemption
+
+The exemptions are tracked in `SyncOperation.syncExemptionFlag`.
+
+- Behavior of `SYNC_EXEMPTION_PROMOTE_BUCKET`
+  - This will add `JobInfo.FLAG_EXEMPT_FROM_APP_STANDBY` to the sync job. This makes the job
+    subject to "ACTIVE" app quota, so minimum deferral will be applied to it.
+
+  - This also reports `AppStandbyController.reportExemptedSyncStart()`, so the package that owns
+    the sync adapter is temporarily put in the "ACTIVE" bucket for the
+    duration of `mExemptedSyncStartTimeoutMillis`, whose default is 10 minutes as of 2020-10-23.
+
+    This will allow the app to access network, even if it has been in the `RARE` bucket
+    (in which case, the system cuts its network access).
+
+    Note if the device is dozing or in battery saver, promoting to the "ACTIVE" bucket will still
+    _not_ give the app network access.
+
+- Behavior of `SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP`
+  - This gives all the perks given by `SYNC_EXEMPTION_PROMOTE_BUCKET`, plus puts the target app
+    in the temp-allowlist (by calling `DeviceIdleInternal.addPowerSaveTempWhitelistApp()`)
+    for the duration of `SyncManagerConstants.getKeyExemptionTempWhitelistDurationInSeconds()`,
+    whose default is 10 minutes.
+
+    Temp-allowlist will grant the app network access even if the device is in doze or in battery
+    saver.
+
+    (However, note that when the device is dozing, sync jobs will not run anyway.)
+
+### How Retries Are Handled
+
+- When a sync operation needs a retry, SyncManager creates a new operation (job) with a back-off
+  (in `SyncManager.maybeRescheduleSync()`). In this case, the new sync operation will inherit
+  `SyncOperation.syncExemptionFlag`, unless the number of retries (not counting the original sync
+  job) is equal to or greater than `SyncManagerConstants.getMaxRetriesWithAppStandbyExemption()`,
+  whose default is 5.
+
+### Special-handling of Pre-installed Packages
+
+- When a content provider is accessed, `AppStandbyController.reportContentProviderUsage()` is
+  triggered, which elevates the standby bucket of the associated sync adapters' packages to `ACTIVE`
+  for the duration of `mSyncAdapterTimeoutMillis`, whose default is 10 minutes, but _only for_
+  pre-installed packages. This is to help pre-installed sync adapters, which often don't have UI,
+  sync properly.
+
+- Also, since Android 11, all the pre-installed apps with no activities will be kept in
+  the `ACTIVE` bucket, which greatly relaxes app-standby throttling. But they're still subject
+  to doze and battery saver.
+
+### Summary
+
+- When the device is dozing, no sync operations will be executed.
+
+- Normally, sync operations are subject to App-Standby, which throttles jobs owned by background
+  apps. Jobs owned by foreground apps are not affected.
+
+- A sync operation requested by a foreground activity will be executed immediately even if the
+  app owning the sync adapter is in RARE bucket, and the device is in battery saver.
+
+- A sync operation requested by a foreground service (or a "bound foreground" service)
+  will be executed immediately even if the app owning the sync adapter is in RARE bucket,
+  *unless* the device is in battery saver.
+
+  Since Android 9 and later, the same thing will happen if the requester is temp-allowlisted (e.g.
+  when it has just received a "high-priority FCM").
+
+- There are certain exemptions for pre-installed apps, but doze and battery saver will still
+  block their sync adapters.
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b10cd12..3ac2185 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1423,17 +1423,18 @@
 
     private Optional<Integer> getViewportType(DisplayDeviceInfo info) {
         // Get the corresponding viewport type.
-        if ((info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
-            return Optional.of(VIEWPORT_INTERNAL);
-        } else if (info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) {
-            return Optional.of(VIEWPORT_EXTERNAL);
-        } else if (info.touch == DisplayDeviceInfo.TOUCH_VIRTUAL
-                && !TextUtils.isEmpty(info.uniqueId)) {
-            return Optional.of(VIEWPORT_VIRTUAL);
-        } else {
-            if (DEBUG) {
-                Slog.i(TAG, "Display " + info + " does not support input device matching.");
-            }
+        switch (info.touch) {
+            case DisplayDeviceInfo.TOUCH_INTERNAL:
+                return Optional.of(VIEWPORT_INTERNAL);
+            case DisplayDeviceInfo.TOUCH_EXTERNAL:
+                return Optional.of(VIEWPORT_EXTERNAL);
+            case DisplayDeviceInfo.TOUCH_VIRTUAL:
+                if (!TextUtils.isEmpty(info.uniqueId)) {
+                    return Optional.of(VIEWPORT_VIRTUAL);
+                }
+                // fallthrough
+            default:
+                Slog.w(TAG, "Display " + info + " does not support input device matching.");
         }
         return Optional.empty();
     }
@@ -1483,13 +1484,6 @@
             return null;
         }
 
-        // Only allow a single INTERNAL or EXTERNAL viewport by forcing their uniqueIds
-        // to be identical (in particular, empty).
-        // TODO (b/116824030) allow multiple EXTERNAL viewports and remove this function.
-        if (viewportType != VIEWPORT_VIRTUAL) {
-            uniqueId = "";
-        }
-
         DisplayViewport viewport;
         final int count = mViewports.size();
         for (int i = 0; i < count; i++) {
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
new file mode 100644
index 0000000..66ec5c4
--- /dev/null
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+    "presubmit": [
+        {
+            "name": "FrameworksMockingServicesTests",
+            "options": [
+                {"include-filter": "com.android.server.display"},
+                {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+                {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
index e906a7c..90d31f2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecConfig.java
@@ -21,6 +21,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringDef;
 import android.content.Context;
 import android.hardware.hdmi.HdmiControlManager;
 import android.os.Environment;
@@ -68,6 +69,15 @@
     private static final int STORAGE_SYSPROPS = 0;
     private static final int STORAGE_GLOBAL_SETTINGS = 1;
 
+    private static final String VALUE_TYPE_STRING = "string";
+    private static final String VALUE_TYPE_INT = "int";
+
+    @StringDef({
+        VALUE_TYPE_STRING,
+        VALUE_TYPE_INT,
+    })
+    private @interface ValueType {}
+
     /**
      * System property key for Power State Change on Active Source Lost.
      */
@@ -247,17 +257,15 @@
         }
     }
 
-    private String retrieveValue(@NonNull Setting setting) {
+    private String retrieveValue(@NonNull Setting setting, @NonNull String defaultValue) {
         @Storage int storage = getStorage(setting);
         String storageKey = getStorageKey(setting);
         if (storage == STORAGE_SYSPROPS) {
             Slog.d(TAG, "Reading '" + storageKey + "' sysprop.");
-            return mStorageAdapter.retrieveSystemProperty(storageKey,
-                    setting.getDefaultValue().getStringValue());
+            return mStorageAdapter.retrieveSystemProperty(storageKey, defaultValue);
         } else if (storage == STORAGE_GLOBAL_SETTINGS) {
             Slog.d(TAG, "Reading '" + storageKey + "' global setting.");
-            return mStorageAdapter.retrieveGlobalSetting(mContext, storageKey,
-                    setting.getDefaultValue().getStringValue());
+            return mStorageAdapter.retrieveGlobalSetting(mContext, storageKey, defaultValue);
         }
         return null;
     }
@@ -316,13 +324,41 @@
     }
 
     /**
-     * For a given setting name returns values that are allowed for that setting.
+     * For a given setting name returns true if and only if the value type of that
+     * setting is a string.
      */
-    public List<String> getAllowedValues(@NonNull @CecSettingName String name) {
+    public boolean isStringValueType(@NonNull @CecSettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
         }
+        return getSetting(name).getValueType().equals(VALUE_TYPE_STRING);
+    }
+
+    /**
+     * For a given setting name returns true if and only if the value type of that
+     * setting is an int.
+     */
+    public boolean isIntValueType(@NonNull @CecSettingName String name) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        return getSetting(name).getValueType().equals(VALUE_TYPE_INT);
+    }
+
+    /**
+     * For a given setting name returns values that are allowed for that setting (string).
+     */
+    public List<String> getAllowedStringValues(@NonNull @CecSettingName String name) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a string-type setting.");
+        }
         List<String> allowedValues = new ArrayList<String>();
         for (Value allowedValue : setting.getAllowedValues().getValue()) {
             allowedValues.add(allowedValue.getStringValue());
@@ -331,32 +367,92 @@
     }
 
     /**
-     * For a given setting name returns the default value for that setting.
+     * For a given setting name returns values that are allowed for that setting (string).
      */
-    public String getDefaultValue(@NonNull @CecSettingName String name) {
+    public List<Integer> getAllowedIntValues(@NonNull @CecSettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
         }
+        if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a string-type setting.");
+        }
+        List<Integer> allowedValues = new ArrayList<Integer>();
+        for (Value allowedValue : setting.getAllowedValues().getValue()) {
+            allowedValues.add(allowedValue.getIntValue());
+        }
+        return allowedValues;
+    }
+
+    /**
+     * For a given setting name returns the default value for that setting (string).
+     */
+    public String getDefaultStringValue(@NonNull @CecSettingName String name) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a string-type setting.");
+        }
         return getSetting(name).getDefaultValue().getStringValue();
     }
 
     /**
-     * For a given setting name returns the current value of that setting.
+     * For a given setting name returns the default value for that setting (int).
      */
-    public String getValue(@NonNull @CecSettingName String name) {
+    public int getDefaultIntValue(@NonNull @CecSettingName String name) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
         }
-        Slog.d(TAG, "Getting CEC setting value '" + name + "'.");
-        return retrieveValue(setting);
+        if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a string-type setting.");
+        }
+        return getSetting(name).getDefaultValue().getIntValue();
     }
 
     /**
-     * For a given setting name and value sets the current value of that setting.
+     * For a given setting name returns the current value of that setting (string).
      */
-    public void setValue(@NonNull @CecSettingName String name, @NonNull String value) {
+    public String getStringValue(@NonNull @CecSettingName String name) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a string-type setting.");
+        }
+        Slog.d(TAG, "Getting CEC setting value '" + name + "'.");
+        return retrieveValue(setting, setting.getDefaultValue().getStringValue());
+    }
+
+    /**
+     * For a given setting name returns the current value of that setting (int).
+     */
+    public int getIntValue(@NonNull @CecSettingName String name) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a int-type setting.");
+        }
+        Slog.d(TAG, "Getting CEC setting value '" + name + "'.");
+        String defaultValue = Integer.toString(setting.getDefaultValue().getIntValue());
+        String value = retrieveValue(setting, defaultValue);
+        return Integer.parseInt(value);
+    }
+
+    /**
+     * For a given setting name and value sets the current value of that setting (string).
+     */
+    public void setStringValue(@NonNull @CecSettingName String name, @NonNull String value) {
         Setting setting = getSetting(name);
         if (setting == null) {
             throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
@@ -364,11 +460,38 @@
         if (!setting.getUserConfigurable()) {
             throw new IllegalArgumentException("Updating CEC setting '" + name + "' prohibited.");
         }
-        if (!getAllowedValues(name).contains(value)) {
+        if (!setting.getValueType().equals(VALUE_TYPE_STRING)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a string-type setting.");
+        }
+        if (!getAllowedStringValues(name).contains(value)) {
             throw new IllegalArgumentException("Invalid CEC setting '" + name
                                                + "' value: '" + value + "'.");
         }
         Slog.d(TAG, "Updating CEC setting '" + name + "' to '" + value + "'.");
         storeValue(setting, value);
     }
+
+    /**
+     * For a given setting name and value sets the current value of that setting (int).
+     */
+    public void setIntValue(@NonNull @CecSettingName String name, int value) {
+        Setting setting = getSetting(name);
+        if (setting == null) {
+            throw new IllegalArgumentException("Setting '" + name + "' does not exist.");
+        }
+        if (!setting.getUserConfigurable()) {
+            throw new IllegalArgumentException("Updating CEC setting '" + name + "' prohibited.");
+        }
+        if (!setting.getValueType().equals(VALUE_TYPE_INT)) {
+            throw new IllegalArgumentException("Setting '" + name
+                    + "' is not a int-type setting.");
+        }
+        if (!getAllowedIntValues(name).contains(value)) {
+            throw new IllegalArgumentException("Invalid CEC setting '" + name
+                                               + "' value: '" + value + "'.");
+        }
+        Slog.d(TAG, "Updating CEC setting '" + name + "' to '" + value + "'.");
+        storeValue(setting, Integer.toString(value));
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index fe4fd38..9dc0079 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -495,17 +495,17 @@
     private byte[] getSupportedShortAudioDescriptorsFromConfig(
             List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes) {
         DeviceConfig deviceConfigToUse = null;
+        String audioDeviceName = SystemProperties.get(
+                Constants.PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT,
+                "VX_AUDIO_DEVICE_IN_HDMI_ARC");
         for (DeviceConfig device : deviceConfig) {
-            // TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
-            if (device.name.equals("VX_AUDIO_DEVICE_IN_HDMI_ARC")) {
+            if (device.name.equals(audioDeviceName)) {
                 deviceConfigToUse = device;
                 break;
             }
         }
         if (deviceConfigToUse == null) {
-            // TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
-            Slog.w(TAG, "sadConfig.xml does not have required device info for "
-                        + "VX_AUDIO_DEVICE_IN_HDMI_ARC");
+            Slog.w(TAG, "sadConfig.xml does not have required device info for " + audioDeviceName);
             return new byte[0];
         }
         HashMap<Integer, byte[]> map = new HashMap<>();
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index da4c6f1..cc0b882 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -2242,9 +2242,15 @@
             List<String> allSettings = hdmiCecConfig.getAllSettings();
             Set<String> userSettings = new HashSet<>(hdmiCecConfig.getUserSettings());
             for (String setting : allSettings) {
-                pw.println(setting + ": " + hdmiCecConfig.getValue(setting)
-                        + " (default: " + hdmiCecConfig.getDefaultValue(setting) + ")"
-                        + (userSettings.contains(setting) ? " [modifiable]" : ""));
+                if (hdmiCecConfig.isStringValueType(setting)) {
+                    pw.println(setting + " (string): " + hdmiCecConfig.getStringValue(setting)
+                            + " (default: " + hdmiCecConfig.getDefaultStringValue(setting) + ")"
+                            + (userSettings.contains(setting) ? " [modifiable]" : ""));
+                } else if (hdmiCecConfig.isIntValueType(setting)) {
+                    pw.println(setting + " (int): " + hdmiCecConfig.getIntValue(setting)
+                            + " (default: " + hdmiCecConfig.getDefaultIntValue(setting) + ")"
+                            + (userSettings.contains(setting) ? " [modifiable]" : ""));
+                }
             }
             pw.decreaseIndent();
 
@@ -2273,33 +2279,68 @@
         }
 
         @Override
-        public List<String> getAllowedCecSettingValues(String name) {
+        public List<String> getAllowedCecSettingStringValues(String name) {
             enforceAccessPermission();
             long token = Binder.clearCallingIdentity();
             try {
-                return HdmiControlService.this.getHdmiCecConfig().getAllowedValues(name);
+                return HdmiControlService.this.getHdmiCecConfig().getAllowedStringValues(name);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override
-        public String getCecSettingValue(String name) {
+        public int[] getAllowedCecSettingIntValues(String name) {
             enforceAccessPermission();
             long token = Binder.clearCallingIdentity();
             try {
-                return HdmiControlService.this.getHdmiCecConfig().getValue(name);
+                List<Integer> allowedValues =
+                        HdmiControlService.this.getHdmiCecConfig().getAllowedIntValues(name);
+                return allowedValues.stream().mapToInt(i->i).toArray();
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
         }
 
         @Override
-        public void setCecSettingValue(String name, String value) {
+        public String getCecSettingStringValue(String name) {
             enforceAccessPermission();
             long token = Binder.clearCallingIdentity();
             try {
-                HdmiControlService.this.getHdmiCecConfig().setValue(name, value);
+                return HdmiControlService.this.getHdmiCecConfig().getStringValue(name);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setCecSettingStringValue(String name, String value) {
+            enforceAccessPermission();
+            long token = Binder.clearCallingIdentity();
+            try {
+                HdmiControlService.this.getHdmiCecConfig().setStringValue(name, value);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public int getCecSettingIntValue(String name) {
+            enforceAccessPermission();
+            long token = Binder.clearCallingIdentity();
+            try {
+                return HdmiControlService.this.getHdmiCecConfig().getIntValue(name);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        @Override
+        public void setCecSettingIntValue(String name, int value) {
+            enforceAccessPermission();
+            long token = Binder.clearCallingIdentity();
+            try {
+                HdmiControlService.this.getHdmiCecConfig().setIntValue(name, value);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9a60afb..683a4b6 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -15,6 +15,7 @@
 
 package com.android.server.inputmethod;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD;
 import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION;
 import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
@@ -110,6 +111,7 @@
 import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.UserManagerInternal;
@@ -3106,6 +3108,7 @@
     @Override
     public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags,
             ResultReceiver resultReceiver) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput");
         int uid = Binder.getCallingUid();
         synchronized (mMethodMap) {
             if (!calledFromValidUserLocked()) {
@@ -3133,6 +3136,7 @@
                         SoftInputShowHideReason.SHOW_SOFT_INPUT);
             } finally {
                 Binder.restoreCallingIdentity(ident);
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
         }
     }
@@ -3225,6 +3229,7 @@
             }
             final long ident = Binder.clearCallingIdentity();
             try {
+                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
                 if (mCurClient == null || client == null
                         || mCurClient.client.asBinder() != client.asBinder()) {
                     // We need to check if this is the current client with
@@ -3248,6 +3253,7 @@
                         SoftInputShowHideReason.HIDE_SOFT_INPUT);
             } finally {
                 Binder.restoreCallingIdentity(ident);
+                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
             }
         }
     }
@@ -3310,43 +3316,52 @@
             Slog.e(TAG, "windowToken cannot be null.");
             return InputBindResult.NULL;
         }
-        final int callingUserId = UserHandle.getCallingUserId();
-        final int userId;
-        if (attribute != null && attribute.targetInputMethodUser != null
-                && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
-            mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
-                    "Using EditorInfo.targetInputMethodUser requires INTERACT_ACROSS_USERS_FULL.");
-            userId = attribute.targetInputMethodUser.getIdentifier();
-            if (!mUserManagerInternal.isUserRunning(userId)) {
-                // There is a chance that we hit here because of race condition.  Let's just return
-                // an error code instead of crashing the caller process, which at least has
-                // INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important process.
-                Slog.e(TAG, "User #" + userId + " is not running.");
-                return InputBindResult.INVALID_USER;
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                    "IMMS.startInputOrWindowGainedFocus");
+            final int callingUserId = UserHandle.getCallingUserId();
+            final int userId;
+            if (attribute != null && attribute.targetInputMethodUser != null
+                    && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
+                mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        "Using EditorInfo.targetInputMethodUser requires"
+                                + " INTERACT_ACROSS_USERS_FULL.");
+                userId = attribute.targetInputMethodUser.getIdentifier();
+                if (!mUserManagerInternal.isUserRunning(userId)) {
+                    // There is a chance that we hit here because of race condition.  Let's just
+                    // return an error code instead of crashing the caller process, which at least
+                    // has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important
+                    // process.
+                    Slog.e(TAG, "User #" + userId + " is not running.");
+                    return InputBindResult.INVALID_USER;
+                }
+            } else {
+                userId = callingUserId;
             }
-        } else {
-            userId = callingUserId;
-        }
-        final InputBindResult result;
-        synchronized (mMethodMap) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
-                        windowToken, startInputFlags, softInputMode, windowFlags, attribute,
-                        inputContext, missingMethods, unverifiedTargetSdkVersion, userId);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
+            final InputBindResult result;
+            synchronized (mMethodMap) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
+                            windowToken, startInputFlags, softInputMode, windowFlags, attribute,
+                            inputContext, missingMethods, unverifiedTargetSdkVersion, userId);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
             }
+            if (result == null) {
+                // This must never happen, but just in case.
+                Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
+                        + InputMethodDebug.startInputReasonToString(startInputReason)
+                        + " windowFlags=#" + Integer.toHexString(windowFlags)
+                        + " editorInfo=" + attribute);
+                return InputBindResult.NULL;
+            }
+
+            return result;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
-        if (result == null) {
-            // This must never happen, but just in case.
-            Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
-                    + InputMethodDebug.startInputReasonToString(startInputReason)
-                    + " windowFlags=#" + Integer.toHexString(windowFlags)
-                    + " editorInfo=" + attribute);
-            return InputBindResult.NULL;
-        }
-        return result;
     }
 
     @NonNull
@@ -4124,6 +4139,7 @@
 
     @BinderThread
     private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -4145,6 +4161,7 @@
                 mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken));
             }
         }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
@@ -4172,6 +4189,7 @@
 
     @BinderThread
     private void hideMySoftInput(@NonNull IBinder token, int flags) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -4186,10 +4204,12 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     @BinderThread
     private void showMySoftInput(@NonNull IBinder token, int flags) {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
         synchronized (mMethodMap) {
             if (!calledWithValidTokenLocked(token)) {
                 return;
@@ -4202,6 +4222,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
         }
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     void setEnabledSessionInMainThread(SessionState session) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 9c48d23..6874f23 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -21,10 +21,10 @@
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.location.LocationManager.BLOCK_PENDING_INTENT_SYSTEM_API_USAGE;
 import static android.location.LocationManager.FUSED_PROVIDER;
 import static android.location.LocationManager.GPS_PROVIDER;
 import static android.location.LocationManager.NETWORK_PROVIDER;
-import static android.location.LocationManager.PREVENT_PENDING_INTENT_SYSTEM_API_USAGE;
 import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
 
 import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -597,14 +597,15 @@
         // simplest to ensure these apis are simply never set for pending intent requests. the same
         // does not apply for listener requests since those will have the process (including the
         // listener) killed on permission removal
-        boolean usesSystemApi = request.isLowPower()
-                || request.isHiddenFromAppOps()
-                || request.isLocationSettingsIgnored()
-                || !request.getWorkSource().isEmpty();
-        if (usesSystemApi
-                && isChangeEnabled(PREVENT_PENDING_INTENT_SYSTEM_API_USAGE, identity.getUid())) {
-            throw new SecurityException(
-                    "PendingIntent location requests may not use system APIs: " + request);
+        if (isChangeEnabled(BLOCK_PENDING_INTENT_SYSTEM_API_USAGE, identity.getUid())) {
+            boolean usesSystemApi = request.isLowPower()
+                    || request.isHiddenFromAppOps()
+                    || request.isLocationSettingsIgnored()
+                    || !request.getWorkSource().isEmpty();
+            if (usesSystemApi) {
+                throw new SecurityException(
+                        "PendingIntent location requests may not use system APIs: " + request);
+            }
         }
 
         request = validateLocationRequest(request, identity);
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index 7a59cba..c23bd85 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -296,15 +296,15 @@
             @Nullable String attributionTag) {
         LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_FINE);
 
-        CallerIdentity callerIdentity = CallerIdentity.fromBinder(mContext, packageName,
+        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName,
                 attributionTag, AppOpsManager.toReceiverId(pendingIntent));
 
-        final long identity = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             putRegistration(new GeofenceKey(pendingIntent, geofence),
-                    new GeofenceRegistration(geofence, callerIdentity, pendingIntent));
+                    new GeofenceRegistration(geofence, identity, pendingIntent));
         } finally {
-            Binder.restoreCallingIdentity(identity);
+            Binder.restoreCallingIdentity(ident);
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationChannelLogger.java b/services/core/java/com/android/server/notification/NotificationChannelLogger.java
index 51faac7..36eec26 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelLogger.java
@@ -215,6 +215,13 @@
     }
 
     /**
+     * @return Small hash of the conversation ID, if present, or 0 otherwise.
+     */
+    static int getConversationIdHash(@NonNull NotificationChannel channel) {
+        return SmallHash.hash(channel.getConversationId());
+    }
+
+    /**
      * @return Small hash of the channel ID, if present, or 0 otherwise.
      */
     static int getIdHash(@NonNull NotificationChannelGroup group) {
diff --git a/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java
index fd3dd56..5a7bc48 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java
@@ -41,7 +41,12 @@
                 /* String package_name */ pkg,
                 /* int32 channel_id_hash */ NotificationChannelLogger.getIdHash(channel),
                 /* int old_importance*/ oldImportance,
-                /* int importance*/ newImportance);
+                /* int importance*/ newImportance,
+                /* bool is_conversation */ channel.isConversation(),
+                /* int32 conversation_id_hash */
+                NotificationChannelLogger.getConversationIdHash(channel),
+                /* bool is_conversation_demoted */ channel.isDemoted(),
+                /* bool is_conversation_priority */ channel.isImportantConversation());
     }
 
     @Override
@@ -53,7 +58,11 @@
                 /* String package_name */ pkg,
                 /* int32 channel_id_hash */ NotificationChannelLogger.getIdHash(channelGroup),
                 /* int old_importance*/ NotificationChannelLogger.getImportance(wasBlocked),
-                /* int importance*/ NotificationChannelLogger.getImportance(channelGroup));
+                /* int importance*/ NotificationChannelLogger.getImportance(channelGroup),
+                /* bool is_conversation */ false,
+                /* int32 conversation_id_hash */ 0,
+                /* bool is_conversation_demoted */ false,
+                /* bool is_conversation_priority */ false);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/IncrementalStates.java b/services/core/java/com/android/server/pm/IncrementalStates.java
index 72803ac..780c522 100644
--- a/services/core/java/com/android/server/pm/IncrementalStates.java
+++ b/services/core/java/com/android/server/pm/IncrementalStates.java
@@ -101,21 +101,18 @@
         if (DEBUG) {
             Slog.i(TAG, "received package commit event");
         }
+        final boolean startableStateChanged;
         synchronized (mLock) {
-            if (!mStartableState.isStartable()) {
-                mStartableState.adoptNewStartableStateLocked(true);
-            }
+            startableStateChanged = mStartableState.adoptNewStartableStateLocked(true);
             if (!isIncremental) {
                 updateProgressLocked(1);
             }
         }
-        mHandler.post(PooledLambda.obtainRunnable(
-                IncrementalStates::reportStartableState,
-                IncrementalStates.this).recycleOnUse());
+        if (startableStateChanged) {
+            onStartableStateChanged();
+        }
         if (!isIncremental) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportFullyLoaded,
-                    IncrementalStates.this).recycleOnUse());
+            onLoadingStateChanged();
         }
     }
 
@@ -131,8 +128,7 @@
         synchronized (mLock) {
             if (mStartableState.isStartable() && mLoadingState.isLoading()) {
                 // Changing from startable -> unstartable only if app is still loading.
-                mStartableState.adoptNewStartableStateLocked(false);
-                startableStateChanged = true;
+                startableStateChanged = mStartableState.adoptNewStartableStateLocked(false);
             } else {
                 // If the app is fully loaded, the crash or ANR is caused by the app itself, so
                 // we do not change the startable state.
@@ -140,12 +136,15 @@
             }
         }
         if (startableStateChanged) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportStartableState,
-                    IncrementalStates.this).recycleOnUse());
+            onStartableStateChanged();
         }
     }
 
+    private void onStartableStateChanged() {
+        // Disable startable state broadcasts
+        // TODO(b/171920377): completely remove unstartable state.
+    }
+
     private void reportStartableState() {
         final Callback callback;
         final boolean startable;
@@ -165,6 +164,12 @@
         }
     }
 
+    private void onLoadingStateChanged() {
+        mHandler.post(PooledLambda.obtainRunnable(
+                IncrementalStates::reportFullyLoaded,
+                IncrementalStates.this).recycleOnUse());
+    }
+
     private void reportFullyLoaded() {
         final Callback callback;
         synchronized (mLock) {
@@ -178,23 +183,20 @@
     private class StatusConsumer implements Consumer<Integer> {
         @Override
         public void accept(Integer storageStatus) {
-            final boolean oldState, newState;
+            final boolean startableStateChanged;
             synchronized (mLock) {
                 if (!mLoadingState.isLoading()) {
                     // Do nothing if the package is already fully loaded
                     return;
                 }
-                oldState = mStartableState.isStartable();
                 mStorageHealthStatus = storageStatus;
-                updateStartableStateLocked();
-                newState = mStartableState.isStartable();
+                startableStateChanged = updateStartableStateLocked();
             }
-            if (oldState != newState) {
-                mHandler.post(PooledLambda.obtainRunnable(IncrementalStates::reportStartableState,
-                        IncrementalStates.this).recycleOnUse());
+            if (startableStateChanged) {
+                onStartableStateChanged();
             }
         }
-    };
+    }
 
     /**
      * By calling this method, the caller indicates that there issues with the Incremental
@@ -239,14 +241,10 @@
             newStartableState = mStartableState.isStartable();
         }
         if (!newLoadingState) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportFullyLoaded,
-                    IncrementalStates.this).recycleOnUse());
+            onLoadingStateChanged();
         }
         if (newStartableState != oldStartableState) {
-            mHandler.post(PooledLambda.obtainRunnable(
-                    IncrementalStates::reportStartableState,
-                    IncrementalStates.this).recycleOnUse());
+            onStartableStateChanged();
         }
     }
 
@@ -284,8 +282,9 @@
      * health
      * status. If the next state is different from the current state, proceed with state
      * change.
+     * @return True if the new startable state is different from the old one.
      */
-    private void updateStartableStateLocked() {
+    private boolean updateStartableStateLocked() {
         final boolean currentState = mStartableState.isStartable();
         boolean nextState = currentState;
         if (!currentState) {
@@ -302,9 +301,9 @@
             }
         }
         if (nextState == currentState) {
-            return;
+            return false;
         }
-        mStartableState.adoptNewStartableStateLocked(nextState);
+        return mStartableState.adoptNewStartableStateLocked(nextState);
     }
 
     private void updateProgressLocked(float progress) {
@@ -343,12 +342,30 @@
             return mUnstartableReason;
         }
 
-        public void adoptNewStartableStateLocked(boolean nextState) {
+        /**
+         * Adopt new startable state if it is different from the current state.
+         * @param nextState True if startable, false if unstartable.
+         * @return True if the state has changed, false otherwise.
+         */
+        public boolean adoptNewStartableStateLocked(boolean nextState) {
+            if (mIsStartable == nextState) {
+                return false;
+            }
+            if (!nextState) {
+                // Do nothing if the next state is "unstartable"; keep package always startable.
+                // TODO(b/171920377): completely remove unstartable state.
+                if (DEBUG) {
+                    Slog.i(TAG, "Attempting to set startable state to false. Abort.");
+                }
+                return false;
+            }
             if (DEBUG) {
-                Slog.i(TAG, "startable state changed from " + mIsStartable + " to " + nextState);
+                Slog.i(TAG,
+                        "startable state changed from " + mIsStartable + " to " + nextState);
             }
             mIsStartable = nextState;
             mUnstartableReason = getUnstartableReasonLocked();
+            return true;
         }
 
         private int getUnstartableReasonLocked() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b3f49ad..203321e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -16478,8 +16478,8 @@
                     healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS;
                     healthCheckParams.unhealthyMonitoringMs =
                             INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS;
-                    mIncrementalManager.registerHealthListener(codePath,
-                            new StorageHealthCheckParams(), incrementalHealthListener);
+                    mIncrementalManager.registerHealthListener(codePath, healthCheckParams,
+                            incrementalHealthListener);
                 }
 
                 // Ensure that the uninstall reason is UNKNOWN for users with the package installed.
@@ -19605,8 +19605,6 @@
                     if (installed) {
                         ps.setUninstallReason(UNINSTALL_REASON_UNKNOWN, userId);
                     }
-
-                    writeRuntimePermissionsForUserLPrTEMP(userId, false);
                 }
                 // Regardless of writeSettings we need to ensure that this restriction
                 // state propagation is persisted
@@ -25751,8 +25749,9 @@
         @Override
         public void writePermissionSettings(int[] userIds, boolean async) {
             synchronized (mLock) {
+                mPermissionManager.writeLegacyPermissionStateTEMP();
                 for (int userId : userIds) {
-                    writeRuntimePermissionsForUserLPrTEMP(userId, !async);
+                    mSettings.writeRuntimePermissionsForUserLPr(userId, !async);
                 }
             }
         }
@@ -26401,17 +26400,6 @@
         mSettings.writeLPr();
     }
 
-    /**
-     * Temporary method that wraps mSettings.writeRuntimePermissionsForUserLPr() and calls
-     * mPermissionManager.writeLegacyPermissionStateTEMP() beforehand.
-     *
-     * TODO(zhanghai): This should be removed once we finish migration of permission storage.
-     */
-    private void writeRuntimePermissionsForUserLPrTEMP(@UserIdInt int userId, boolean async) {
-        mPermissionManager.writeLegacyPermissionStateTEMP();
-        mSettings.writeRuntimePermissionsForUserLPr(userId, async);
-    }
-
     @Override
     public IBinder getHoldLockToken() {
         if (!Build.IS_DEBUGGABLE) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7f29cd9..24082b8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3993,7 +3993,7 @@
     @Override
     public @UserManager.RemoveResult int removeUserOrSetEphemeral(@UserIdInt int userId) {
         Slog.i(LOG_TAG, "removeUserOrSetEphemeral u" + userId);
-        checkManageUsersPermission("Only the system can remove users");
+        checkManageOrCreateUsersPermission("Only the system can remove users");
         final String restriction = getUserRemovalRestriction(userId);
         if (getUserRestrictions(UserHandle.getCallingUserId()).getBoolean(restriction, false)) {
             Slog.w(LOG_TAG, "Cannot remove user. " + restriction + " is enabled.");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8b677a9..fcba5ce 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2429,6 +2429,15 @@
             wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
             view = win.getDecorView();
 
+            // Ignore to show splash screen if the decorView is not opaque.
+            if (!view.isOpaque()) {
+                if (DEBUG_SPLASH_SCREEN) {
+                    Slog.d(TAG, "addSplashScreen: the view of " + packageName
+                            + " is not opaque, cancel it");
+                }
+                return null;
+            }
+
             if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
                 + packageName + " / " + appToken + ": " + (view.getParent() != null ? view : null));
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f65a5024..19b13c1 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -194,7 +194,7 @@
 import static com.android.server.wm.Task.ActivityState.STARTED;
 import static com.android.server.wm.Task.ActivityState.STOPPED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
 import static com.android.server.wm.TaskPersister.DEBUG;
 import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
@@ -417,7 +417,7 @@
     // mOccludesParent field.
     final boolean hasWallpaper;
     // Input application handle used by the input dispatcher.
-    final InputApplicationHandle mInputApplicationHandle;
+    private InputApplicationHandle mInputApplicationHandle;
 
     final int launchedFromPid; // always the pid who started the activity.
     final int launchedFromUid; // always the uid who started the activity.
@@ -1506,7 +1506,6 @@
         info = aInfo;
         mUserId = UserHandle.getUserId(info.applicationInfo.uid);
         packageName = info.applicationInfo.packageName;
-        mInputApplicationHandle = new InputApplicationHandle(appToken);
         intent = _intent;
 
         // If the class name in the intent doesn't match that of the target, this is probably an
@@ -1693,6 +1692,21 @@
         return lockTaskLaunchMode;
     }
 
+    @NonNull InputApplicationHandle getInputApplicationHandle(boolean update) {
+        if (mInputApplicationHandle == null) {
+            mInputApplicationHandle = new InputApplicationHandle(appToken, toString(),
+                    mInputDispatchingTimeoutMillis);
+        } else if (update) {
+            final String name = toString();
+            if (mInputDispatchingTimeoutMillis != mInputApplicationHandle.dispatchingTimeoutMillis
+                    || !name.equals(mInputApplicationHandle.name)) {
+                mInputApplicationHandle = new InputApplicationHandle(appToken, name,
+                        mInputDispatchingTimeoutMillis);
+            }
+        }
+        return mInputApplicationHandle;
+    }
+
     @Override
     ActivityRecord asActivityRecord() {
         // I am an activity record!
@@ -4881,7 +4895,7 @@
      */
     private boolean shouldBeResumed(ActivityRecord activeActivity) {
         return shouldMakeActive(activeActivity) && isFocusable()
-                && getTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
+                && getTask().getVisibility(activeActivity) == TASK_VISIBILITY_VISIBLE
                 && canResumeByCompat();
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a068d2b..17209eb 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -50,12 +50,12 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_IDLE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_IDLE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -63,17 +63,17 @@
 import static com.android.server.wm.ActivityTaskManagerService.ANIMATE;
 import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_SUPERVISOR_STACK_MSG;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.Task.ActivityState.PAUSED;
 import static com.android.server.wm.Task.ActivityState.PAUSING;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_ALLOWLISTED;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
 import static com.android.server.wm.Task.TAG_CLEANUP;
 import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -158,7 +158,7 @@
     private static final String TAG_IDLE = TAG + POSTFIX_IDLE;
     private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
     private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS;
-    private static final String TAG_STACK = TAG + POSTFIX_STACK;
+    private static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
     private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
     static final String TAG_TASKS = TAG + POSTFIX_TASKS;
 
@@ -526,7 +526,7 @@
         int candidateTaskId = nextTaskIdForUser(currentTaskId, userId);
         while (mRecentTasks.containsTaskId(candidateTaskId, userId)
                 || mRootWindowContainer.anyTaskForId(
-                        candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
+                        candidateTaskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS) != null) {
             candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);
             if (candidateTaskId == currentTaskId) {
                 // Something wrong!
@@ -1360,8 +1360,8 @@
 
             if (stack != currentStack) {
                 moveHomeStackToFrontIfNeeded(flags, stack.getDisplayArea(), reason);
-                task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
-                        reason);
+                task.reparent(stack, ON_TOP, REPARENT_KEEP_ROOT_TASK_AT_FRONT, !ANIMATE,
+                        DEFER_RESUME, reason);
                 currentStack = stack;
                 reparented = true;
                 // task.reparent() should already placed the task on top,
@@ -1385,7 +1385,7 @@
         currentStack.moveTaskToFront(task, false /* noAnimation */, options,
                 r == null ? null : r.appTimeTracker, reason);
 
-        if (DEBUG_STACK) Slog.d(TAG_STACK,
+        if (DEBUG_ROOT_TASK) Slog.d(TAG_ROOT_TASK,
                 "findTaskToMoveToFront: moved to front of stack=" + currentStack);
 
         handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
@@ -1502,7 +1502,7 @@
     boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
             String reason) {
         final Task task =
-                mRootWindowContainer.anyTaskForId(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
         if (task != null) {
             removeTask(task, killProcess, removeFromRecents, reason);
             return true;
@@ -2478,7 +2478,7 @@
         mService.deferWindowLayout();
         try {
             task = mRootWindowContainer.anyTaskForId(taskId,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
+                    MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, activityOptions, ON_TOP);
             if (task == null) {
                 mWindowManager.executeAppTransition();
                 throw new IllegalArgumentException(
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 910a1a2..33819a9 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -75,7 +75,7 @@
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import android.annotation.NonNull;
@@ -1665,11 +1665,6 @@
             final Task taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
                     ? mSourceRecord.getTask() : null;
             setNewTask(taskToAffiliate);
-            if (mService.getLockTaskController().isLockTaskModeViolation(
-                    mStartActivity.getTask())) {
-                Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
-            }
         } else if (mAddingToTask) {
             addOrReparentStartingActivity(targetTask, "adding to task");
         }
@@ -1848,10 +1843,17 @@
         final boolean isNewClearTask =
                 (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
                         == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-        if (!newTask && mService.getLockTaskController().isLockTaskModeViolation(targetTask,
-                isNewClearTask)) {
-            Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
-            return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+        if (!newTask) {
+            if (mService.getLockTaskController().isLockTaskModeViolation(targetTask,
+                    isNewClearTask)) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+        } else {
+            if (mService.getLockTaskController().isNewTaskLockTaskModeViolation(mStartActivity)) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
+                return START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
         }
 
         return START_SUCCESS;
@@ -2530,8 +2532,8 @@
                             "bringingFoundTaskToFront");
                     mMovedToFront = !isSplitScreenTopStack;
                 } else {
-                    intentTask.reparent(launchStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
-                            DEFER_RESUME, "reparentToTargetStack");
+                    intentTask.reparent(launchStack, ON_TOP, REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+                            ANIMATE, DEFER_RESUME, "reparentToTargetStack");
                     mMovedToFront = true;
                 }
                 mOptions = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
index b5675a9..33d1b44 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerDebugConfig.java
@@ -45,7 +45,7 @@
 
     static final boolean DEBUG_RECENTS = DEBUG_ALL || false;
     static final boolean DEBUG_RECENTS_TRIM_TASKS = DEBUG_RECENTS || false;
-    static final boolean DEBUG_STACK = DEBUG_ALL || false;
+    static final boolean DEBUG_ROOT_TASK = DEBUG_ALL || false;
     public static final boolean DEBUG_SWITCH = DEBUG_ALL || false;
     static final boolean DEBUG_TRANSITION = DEBUG_ALL || false;
     static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false;
@@ -73,7 +73,7 @@
     static final String POSTFIX_PAUSE = APPEND_CATEGORY_NAME ? "_Pause" : "";
     static final String POSTFIX_RECENTS = APPEND_CATEGORY_NAME ? "_Recents" : "";
     static final String POSTFIX_SAVED_STATE = APPEND_CATEGORY_NAME ? "_SavedState" : "";
-    static final String POSTFIX_STACK = APPEND_CATEGORY_NAME ? "_Stack" : "";
+    static final String POSTFIX_ROOT_TASK = APPEND_CATEGORY_NAME ? "_RootTask" : "";
     static final String POSTFIX_STATES = APPEND_CATEGORY_NAME ? "_States" : "";
     public static final String POSTFIX_SWITCH = APPEND_CATEGORY_NAME ? "_Switch" : "";
     static final String POSTFIX_TASKS = APPEND_CATEGORY_NAME ? "_Tasks" : "";
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 581b367..73c4713 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -106,7 +106,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_IMMERSIVE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_VISIBILITY;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
@@ -117,14 +117,14 @@
 import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
 import static com.android.server.wm.ActivityTaskManagerService.H.REPORT_TIME_TRACKER_MSG;
 import static com.android.server.wm.ActivityTaskManagerService.UiHandler.DISMISS_DIALOG_UI_MSG;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION;
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_ONLY;
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_ONLY;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 import static com.android.server.wm.Task.ActivityState.DESTROYED;
 import static com.android.server.wm.Task.ActivityState.DESTROYING;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_DONT_LOCK;
-import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
@@ -306,7 +306,7 @@
  */
 public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityTaskManagerService" : TAG_ATM;
-    static final String TAG_STACK = TAG + POSTFIX_STACK;
+    static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
     static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
     private static final String TAG_IMMERSIVE = TAG + POSTFIX_IMMERSIVE;
     private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS;
@@ -2228,7 +2228,7 @@
         try {
             synchronized (mGlobalLock) {
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
+                        MATCH_ATTACHED_TASK_ONLY);
                 if (task == null) {
                     return;
                 }
@@ -2266,7 +2266,7 @@
             final long ident = Binder.clearCallingIdentity();
             try {
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
                 if (task == null) {
                     Slog.w(TAG, "removeTask: No task remove with id=" + taskId);
                     return false;
@@ -2374,7 +2374,7 @@
         try {
             synchronized (mGlobalLock) {
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
                 if (task == null) {
                     Slog.w(TAG, "getTaskBounds: taskId=" + taskId + " not found");
                     return rect;
@@ -2397,7 +2397,7 @@
             enforceCallerIsRecentsOrHasPermission(
                     MANAGE_ACTIVITY_TASKS, "getTaskDescription()");
             final Task tr = mRootWindowContainer.anyTaskForId(id,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                    MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
             if (tr != null) {
                 return tr.getTaskDescription();
             }
@@ -2418,7 +2418,7 @@
                     return setTaskWindowingModeSplitScreen(taskId, windowingMode, toTop);
                 }
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
+                        MATCH_ATTACHED_TASK_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "setTaskWindowingMode: No task for id=" + taskId);
                     return false;
@@ -2789,8 +2789,8 @@
                     throw new IllegalArgumentException("moveTaskToRootTask: Attempt to move task "
                             + taskId + " to rootTask " + rootTaskId);
                 }
-                task.reparent(rootTask, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
-                        "moveTaskToRootTask");
+                task.reparent(rootTask, toTop, REPARENT_KEEP_ROOT_TASK_AT_FRONT, ANIMATE,
+                        !DEFER_RESUME, "moveTaskToRootTask");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2834,7 +2834,7 @@
         }
 
         final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                MATCH_TASK_IN_STACKS_ONLY);
+                MATCH_ATTACHED_TASK_ONLY);
         if (task == null) {
             Slog.w(TAG, "setTaskWindowingModeSplitScreenPrimary: No task for id=" + taskId);
             return false;
@@ -3013,7 +3013,7 @@
         try {
             synchronized (mGlobalLock) {
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
+                        MATCH_ATTACHED_TASK_ONLY);
                 if (task == null) {
                     return;
                 }
@@ -3361,7 +3361,7 @@
     public void setTaskResizeable(int taskId, int resizeableMode) {
         synchronized (mGlobalLock) {
             final Task task = mRootWindowContainer.anyTaskForId(
-                    taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                    taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
             if (task == null) {
                 Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");
                 return;
@@ -3377,7 +3377,7 @@
         try {
             synchronized (mGlobalLock) {
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
+                        MATCH_ATTACHED_TASK_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
                     return false;
@@ -4395,7 +4395,7 @@
         try {
             synchronized (mGlobalLock) {
                 final Task task = mRootWindowContainer.anyTaskForId(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY);
+                        MATCH_ATTACHED_TASK_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "cancelTaskWindowTransition: taskId=" + taskId + " not found");
                     return;
@@ -4423,7 +4423,7 @@
         final Task task;
         synchronized (mGlobalLock) {
             task = mRootWindowContainer.anyTaskForId(taskId,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                    MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
             if (task == null) {
                 Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
                 return null;
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 3e79879..fbbda59 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -17,7 +17,7 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 
 import android.app.ActivityManager;
 import android.app.IAppTask;
@@ -79,7 +79,7 @@
             final long origId = Binder.clearCallingIdentity();
             try {
                 Task task = mService.mRootWindowContainer.anyTaskForId(mTaskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
                 if (task == null) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
@@ -136,7 +136,7 @@
         IApplicationThread appThread;
         synchronized (mService.mGlobalLock) {
             task = mService.mRootWindowContainer.anyTaskForId(mTaskId,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                    MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
             if (task == null) {
                 throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
             }
@@ -165,7 +165,7 @@
             final long origId = Binder.clearCallingIdentity();
             try {
                 Task task = mService.mRootWindowContainer.anyTaskForId(mTaskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
+                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
                 if (task == null) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6453ddf..3a4eb09 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -88,7 +88,6 @@
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
 import static com.android.server.wm.DisplayContentProto.CAN_SHOW_IME;
 import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
@@ -238,7 +237,6 @@
  */
 class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
-    private static final String TAG_STACK = TAG + POSTFIX_STACK;
 
     /** The default scaling mode that scales content automatically. */
     static final int FORCE_SCALING_MODE_AUTO = 0;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 2ea4b57..8c80205 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -277,9 +277,8 @@
             mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
                     mService.mH.getLooper(), mDragDropController);
 
-            mDragApplicationHandle = new InputApplicationHandle(new Binder());
-            mDragApplicationHandle.name = "drag";
-            mDragApplicationHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+            mDragApplicationHandle = new InputApplicationHandle(new Binder(), "drag",
+                    DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
 
             mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
                     display.getDisplayId());
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index 3b89a24..b08d6e1 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -175,11 +175,11 @@
 
         InputApplicationHandle getApplicationHandle() {
             if (mHostWindowState == null
-                    || mHostWindowState.mInputWindowHandle.inputApplicationHandle == null) {
+                    || mHostWindowState.mInputWindowHandle.getInputApplicationHandle() == null) {
                 return null;
             }
             return new InputApplicationHandle(
-                    mHostWindowState.mInputWindowHandle.inputApplicationHandle);
+                    mHostWindowState.mInputWindowHandle.getInputApplicationHandle());
         }
 
         InputChannel openInputChannel() {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 0813b4f..818d96c 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -16,11 +16,14 @@
 
 package com.android.server.wm;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME;
 import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER;
 import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN;
 
+import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSource;
 import android.view.WindowInsets;
@@ -79,6 +82,7 @@
                 ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s",
                         target.getWindow() != null ? target.getWindow().getName() : "");
                 target.showInsets(WindowInsets.Type.ime(), true /* fromIme */);
+                Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 if (target != mImeTargetFromIme && mImeTargetFromIme != null) {
                     ProtoLog.w(WM_DEBUG_IME,
                             "showInsets(ime) was requested by different window: %s ",
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index edb5e85..e35621a 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -63,9 +63,8 @@
             mClientChannel.copyTo(inputChannel);
         }
 
-        mApplicationHandle = new InputApplicationHandle(new Binder());
-        mApplicationHandle.name = name;
-        mApplicationHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
+        mApplicationHandle = new InputApplicationHandle(new Binder(), name,
+                DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
 
         mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
         mWindowHandle.name = name;
@@ -160,9 +159,11 @@
     public void binderDied() {
         synchronized (mService.getWindowManagerLock()) {
             // Clean up the input consumer
-            final InputMonitor inputMonitor =
-                    mService.mRoot.getDisplayContent(mWindowHandle.displayId).getInputMonitor();
-            inputMonitor.destroyInputConsumer(mName);
+            final DisplayContent dc = mService.mRoot.getDisplayContent(mWindowHandle.displayId);
+            if (dc == null) {
+                return;
+            }
+            dc.getInputMonitor().destroyInputConsumer(mName);
             unlinkFromDeathRecipient();
         }
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 4a54196..43c4741 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -48,6 +48,7 @@
 import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
 
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -57,12 +58,12 @@
 import android.util.ArrayMap;
 import android.util.EventLog;
 import android.util.Slog;
-import android.view.InputApplicationHandle;
 import android.view.InputChannel;
 import android.view.InputEventReceiver;
 import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
 
 import java.io.PrintWriter;
@@ -81,7 +82,7 @@
     private boolean mUpdateInputWindowsImmediately;
 
     private boolean mDisableWallpaperTouchEvents;
-    private final Rect mTmpRect = new Rect();
+    private final Region mTmpRegion = new Region();
     private final UpdateInputForAllWindowsConsumer mUpdateInputForAllWindowsConsumer;
 
     private final int mDisplayId;
@@ -276,66 +277,66 @@
         addInputConsumer(name, consumer);
     }
 
-
-    void populateInputWindowHandle(final InputWindowHandle inputWindowHandle,
-            final WindowState child, int flags, final int type, final boolean isVisible,
-            final boolean focusable, final boolean hasWallpaper) {
+    @VisibleForTesting
+    void populateInputWindowHandle(final InputWindowHandleWrapper inputWindowHandle,
+            final WindowState w) {
         // Add a window to our list of input windows.
-        inputWindowHandle.name = child.toString();
-        flags = child.getSurfaceTouchableRegion(inputWindowHandle, flags);
-        inputWindowHandle.layoutParamsFlags = flags;
-        inputWindowHandle.layoutParamsType = type;
-        inputWindowHandle.dispatchingTimeoutMillis = child.getInputDispatchingTimeoutMillis();
-        inputWindowHandle.visible = isVisible;
-        inputWindowHandle.focusable = focusable;
-        inputWindowHandle.touchOcclusionMode = child.getTouchOcclusionMode();
-        inputWindowHandle.hasWallpaper = hasWallpaper;
-        inputWindowHandle.paused = child.mActivityRecord != null ? child.mActivityRecord.paused : false;
-        inputWindowHandle.ownerPid = child.mSession.mPid;
-        inputWindowHandle.ownerUid = child.mSession.mUid;
-        inputWindowHandle.packageName = child.getOwningPackage();
-        inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures;
-        inputWindowHandle.displayId = child.getDisplayId();
+        inputWindowHandle.setInputApplicationHandle(w.mActivityRecord != null
+                ? w.mActivityRecord.getInputApplicationHandle(false /* update */) : null);
+        inputWindowHandle.setToken(w.mInputChannelToken);
+        inputWindowHandle.setDispatchingTimeoutMillis(w.getInputDispatchingTimeoutMillis());
+        inputWindowHandle.setTouchOcclusionMode(w.getTouchOcclusionMode());
+        inputWindowHandle.setInputFeatures(w.mAttrs.inputFeatures);
+        inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
+        inputWindowHandle.setVisible(w.isVisible());
 
-        final Rect frame = child.getFrame();
-        inputWindowHandle.frameLeft = frame.left;
-        inputWindowHandle.frameTop = frame.top;
-        inputWindowHandle.frameRight = frame.right;
-        inputWindowHandle.frameBottom = frame.bottom;
+        final boolean focusable = w.canReceiveKeys()
+                && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
+        inputWindowHandle.setFocusable(focusable);
+
+        final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
+                && !mService.mPolicy.isKeyguardShowing()
+                && !mDisableWallpaperTouchEvents;
+        inputWindowHandle.setHasWallpaper(hasWallpaper);
+
+        final Rect frame = w.getFrame();
+        inputWindowHandle.setFrame(frame.left, frame.top, frame.right, frame.bottom);
 
         // Surface insets are hardcoded to be the same in all directions
         // and we could probably deprecate the "left/right/top/bottom" concept.
         // we avoid reintroducing this concept by just choosing one of them here.
-        inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left;
+        inputWindowHandle.setSurfaceInset(w.mAttrs.surfaceInsets.left);
 
-        /**
-         * If the window is in a TaskManaged by a TaskOrganizer then most cropping
-         * will be applied using the SurfaceControl hierarchy from the Organizer.
-         * This means we need to make sure that these changes in crop are reflected
-         * in the input windows, and so ensure this flag is set so that
-         * the input crop always reflects the surface hierarchy.
-         *
-         * TODO(b/168252846): we have some issues with modal-windows, so we need to
-         * cross that bridge now that we organize full-screen Tasks.
-         */
-        if (child.getTask() != null
-                && child.getTask().isOrganized()
-                && child.getTask().getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
-            inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */);
+        // If we are scaling the window, input coordinates need to be inversely scaled to map from
+        // what is on screen to what is actually being touched in the UI.
+        inputWindowHandle.setScaleFactor(w.mGlobalScale != 1f ? (1f / w.mGlobalScale) : 1f);
+
+        final int flags = w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs.flags);
+        inputWindowHandle.setTouchableRegion(mTmpRegion);
+        inputWindowHandle.setLayoutParamsFlags(flags);
+
+        boolean useSurfaceCrop = false;
+        final Task task = w.getTask();
+        if (task != null) {
+            if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+                // If the window is in a TaskManaged by a TaskOrganizer then most cropping will
+                // be applied using the SurfaceControl hierarchy from the Organizer. This means
+                // we need to make sure that these changes in crop are reflected in the input
+                // windows, and so ensure this flag is set so that the input crop always reflects
+                // the surface hierarchy.
+                // TODO(b/168252846): we have some issues with modal-windows, so we need to cross
+                // that bridge now that we organize full-screen Tasks.
+                inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */);
+                useSurfaceCrop = true;
+            } else if (task.cropWindowsToStackBounds() && !w.inFreeformWindowingMode()) {
+                inputWindowHandle.replaceTouchableRegionWithCrop(
+                        task.getRootTask().getSurfaceControl());
+                useSurfaceCrop = true;
+            }
         }
-
-        if (child.mGlobalScale != 1) {
-            // If we are scaling the window, input coordinates need
-            // to be inversely scaled to map from what is on screen
-            // to what is actually being touched in the UI.
-            inputWindowHandle.scaleFactor = 1.0f/child.mGlobalScale;
-        } else {
-            inputWindowHandle.scaleFactor = 1;
-        }
-
-        if (DEBUG_INPUT) {
-            Slog.d(TAG_WM, "addInputWindowHandle: "
-                    + child + ", " + inputWindowHandle);
+        if (!useSurfaceCrop) {
+            inputWindowHandle.setReplaceTouchableRegionWithCrop(false);
+            inputWindowHandle.setTouchableRegionCrop(null);
         }
     }
 
@@ -401,15 +402,8 @@
 
     public void setFocusedAppLw(ActivityRecord newApp) {
         // Focused app has changed.
-        if (newApp == null) {
-            mService.mInputManager.setFocusedApplication(mDisplayId, null);
-        } else {
-            final InputApplicationHandle handle = newApp.mInputApplicationHandle;
-            handle.name = newApp.toString();
-            handle.dispatchingTimeoutMillis = newApp.mInputDispatchingTimeoutMillis;
-
-            mService.mInputManager.setFocusedApplication(mDisplayId, handle);
-        }
+        mService.mInputManager.setFocusedApplication(mDisplayId,
+                newApp != null ? newApp.getInputApplicationHandle(true /* update */) : null);
     }
 
     public void pauseDispatchingLw(WindowToken window) {
@@ -456,10 +450,6 @@
         private boolean mAddRecentsAnimationInputConsumerHandle;
 
         boolean mInDrag;
-        WallpaperController mWallpaperController;
-
-        // An invalid window handle that tells SurfaceFlinger not update the input info.
-        final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, mDisplayId);
 
         private void updateInputWindows(boolean inDrag) {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
@@ -474,10 +464,8 @@
             mAddWallpaperInputConsumerHandle = mWallpaperInputConsumer != null;
             mAddRecentsAnimationInputConsumerHandle = mRecentsAnimationInputConsumer != null;
 
-            mTmpRect.setEmpty();
             mDisableWallpaperTouchEvents = false;
             mInDrag = inDrag;
-            mWallpaperController = mDisplayContent.mWallpaperController;
 
             resetInputConsumers(mInputTransaction);
 
@@ -499,7 +487,7 @@
             }
 
             final WindowState focus = mDisplayContent.mCurrentFocus;
-            if (focus == null || focus.mInputWindowHandle.token == null) {
+            if (focus == null || focus.mInputChannelToken == null) {
                 mDisplayContent.mLastRequestedFocus = focus;
                 return;
             }
@@ -510,7 +498,7 @@
                 return;
             }
 
-            mInputTransaction.setFocusedWindow(focus.mInputWindowHandle.token, mDisplayId);
+            mInputTransaction.setFocusedWindow(focus.mInputChannelToken, mDisplayId);
             EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
                     "Focus request " + focus, "reason=UpdateInputWindows");
             mDisplayContent.mLastRequestedFocus = focus;
@@ -519,33 +507,26 @@
 
         @Override
         public void accept(WindowState w) {
-            final InputChannel inputChannel = w.mInputChannel;
-            final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
+            final InputWindowHandleWrapper inputWindowHandle = w.mInputWindowHandle;
             final RecentsAnimationController recentsAnimationController =
                     mService.getRecentsAnimationController();
             final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
                     && recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
-            final int type = w.mAttrs.type;
-            final boolean isVisible = w.isVisibleLw();
-            if (inputChannel == null || inputWindowHandle == null || w.mRemoved
+            if (w.mInputChannelToken == null || w.mRemoved
                     || (!w.canReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
                 if (w.mWinAnimator.hasSurface()) {
                     // Assign an InputInfo with type to the overlay window which can't receive input
                     // event. This is used to omit Surfaces from occlusion detection.
-                    populateOverlayInputInfo(mInvalidInputWindow, w.getName(), type, isVisible);
-                    mInputTransaction.setInputWindowInfo(
-                            w.mWinAnimator.mSurfaceController.mSurfaceControl,
-                            mInvalidInputWindow);
+                    populateOverlayInputInfo(inputWindowHandle, w.isVisible());
+                    setInputWindowInfoIfNeeded(mInputTransaction,
+                            w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
                     return;
                 }
                 // Skip this window because it cannot possibly receive input.
                 return;
             }
 
-            final int flags = w.mAttrs.flags;
             final int privateFlags = w.mAttrs.privateFlags;
-            final boolean focusable = w.canReceiveKeys()
-                    && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
 
             if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
                 if (recentsAnimationController.updateInputConsumerForApp(
@@ -584,47 +565,53 @@
             if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
                 mDisableWallpaperTouchEvents = true;
             }
-            final boolean hasWallpaper = mWallpaperController.isWallpaperTarget(w)
-                    && !mService.mPolicy.isKeyguardShowing()
-                    && !mDisableWallpaperTouchEvents;
 
             // If there's a drag in progress and 'child' is a potential drop target,
             // make sure it's been told about the drag
-            if (mInDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
+            if (mInDrag && w.isVisible() && w.getDisplayContent().isDefaultDisplay) {
                 mService.mDragDropController.sendDragStartedIfNeededLocked(w);
             }
 
-            populateInputWindowHandle(
-                    inputWindowHandle, w, flags, type, isVisible, focusable, hasWallpaper);
-
             // register key interception info
-            mService.mKeyInterceptionInfoForToken.put(inputWindowHandle.token,
+            mService.mKeyInterceptionInfoForToken.put(w.mInputChannelToken,
                     w.getKeyInterceptionInfo());
 
             if (w.mWinAnimator.hasSurface()) {
-                mInputTransaction.setInputWindowInfo(
+                populateInputWindowHandle(inputWindowHandle, w);
+                setInputWindowInfoIfNeeded(mInputTransaction,
                         w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
             }
         }
     }
 
+    @VisibleForTesting
+    static void setInputWindowInfoIfNeeded(SurfaceControl.Transaction t, SurfaceControl sc,
+            InputWindowHandleWrapper inputWindowHandle) {
+        if (DEBUG_INPUT) {
+            Slog.d(TAG_WM, "Update InputWindowHandle: " + inputWindowHandle);
+        }
+        if (inputWindowHandle.isChanged()) {
+            inputWindowHandle.applyChangesToSurface(t, sc);
+        }
+    }
+
     // This would reset InputWindowHandle fields to prevent it could be found by input event.
     // We need to check if any new field of InputWindowHandle could impact the result.
-    private static void populateOverlayInputInfo(final InputWindowHandle inputWindowHandle,
-            final String name, final int type, final boolean isVisible) {
-        inputWindowHandle.name = name;
-        inputWindowHandle.layoutParamsType = type;
-        inputWindowHandle.dispatchingTimeoutMillis = 0; // it should never receive input
-        inputWindowHandle.visible = isVisible;
-        inputWindowHandle.focusable = false;
-        inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL;
-        inputWindowHandle.scaleFactor = 1;
-        inputWindowHandle.layoutParamsFlags =
-                FLAG_NOT_TOUCH_MODAL | FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE;
-        inputWindowHandle.portalToDisplayId = INVALID_DISPLAY;
-        inputWindowHandle.touchableRegion.setEmpty();
+    @VisibleForTesting
+    static void populateOverlayInputInfo(InputWindowHandleWrapper inputWindowHandle,
+            boolean isVisible) {
+        inputWindowHandle.setDispatchingTimeoutMillis(0); // It should never receive input.
+        inputWindowHandle.setVisible(isVisible);
+        inputWindowHandle.setFocusable(false);
+        inputWindowHandle.setInputFeatures(INPUT_FEATURE_NO_INPUT_CHANNEL);
+        // The input window handle without input channel must not have a token.
+        inputWindowHandle.setToken(null);
+        inputWindowHandle.setScaleFactor(1f);
+        inputWindowHandle.setLayoutParamsFlags(
+                FLAG_NOT_TOUCH_MODAL | FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE);
+        inputWindowHandle.setPortalToDisplayId(INVALID_DISPLAY);
+        inputWindowHandle.clearTouchableRegion();
         inputWindowHandle.setTouchableRegionCrop(null);
-        inputWindowHandle.trustedOverlay = isTrustedOverlay(type);
     }
 
     /**
@@ -635,9 +622,13 @@
      */
     static void setTrustedOverlayInputInfo(SurfaceControl sc, SurfaceControl.Transaction t,
             int displayId, String name) {
-        InputWindowHandle inputWindowHandle = new InputWindowHandle(null, displayId);
-        populateOverlayInputInfo(inputWindowHandle, name, TYPE_SECURE_SYSTEM_OVERLAY, true);
-        t.setInputWindowInfo(sc, inputWindowHandle);
+        final InputWindowHandleWrapper inputWindowHandle = new InputWindowHandleWrapper(
+                new InputWindowHandle(null /* inputApplicationHandle */, displayId));
+        inputWindowHandle.setName(name);
+        inputWindowHandle.setLayoutParamsType(TYPE_SECURE_SYSTEM_OVERLAY);
+        inputWindowHandle.setTrustedOverlay(true);
+        populateOverlayInputInfo(inputWindowHandle, true /* isVisible */);
+        setInputWindowInfoIfNeeded(t, sc, inputWindowHandle);
     }
 
     static boolean isTrustedOverlay(int type) {
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
new file mode 100644
index 0000000..1fbeb1f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -0,0 +1,283 @@
+/*
+ * 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.server.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.view.InputApplicationHandle;
+import android.view.InputWindowHandle;
+import android.view.SurfaceControl;
+
+import java.util.Objects;
+
+/**
+ * The wrapper of {@link InputWindowHandle} with field change detection to reduce unnecessary
+ * updates to surface, e.g. if there are no changes, then skip invocation of
+ * {@link SurfaceControl.Transaction#setInputWindowInfo(SurfaceControl, InputWindowHandle)}.
+ */
+class InputWindowHandleWrapper {
+    /** The wrapped handle should not be directly exposed to avoid untracked changes. */
+    private final @NonNull InputWindowHandle mHandle;
+
+    /** Whether the {@link #mHandle} is changed. */
+    private boolean mChanged = true;
+
+    InputWindowHandleWrapper(@NonNull InputWindowHandle handle) {
+        mHandle = handle;
+    }
+
+    /**
+     * Returns {@code true} if the input window handle has changed since the last invocation of
+     * {@link #applyChangesToSurface(SurfaceControl.Transaction, SurfaceControl)}}
+     */
+    boolean isChanged() {
+        return mChanged;
+    }
+
+    void forceChange() {
+        mChanged = true;
+    }
+
+    void applyChangesToSurface(@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl sc) {
+        t.setInputWindowInfo(sc, mHandle);
+        mChanged = false;
+    }
+
+    int getDisplayId() {
+        return mHandle.displayId;
+    }
+
+    InputApplicationHandle getInputApplicationHandle() {
+        return mHandle.inputApplicationHandle;
+    }
+
+    void setInputApplicationHandle(InputApplicationHandle handle) {
+        if (mHandle.inputApplicationHandle == handle) {
+            return;
+        }
+        mHandle.inputApplicationHandle = handle;
+        mChanged = true;
+    }
+
+    void setToken(IBinder token) {
+        if (mHandle.token == token) {
+            return;
+        }
+        mHandle.token = token;
+        mChanged = true;
+    }
+
+    void setName(String name) {
+        if (Objects.equals(mHandle.name, name)) {
+            return;
+        }
+        mHandle.name = name;
+        mChanged = true;
+    }
+
+    void setLayoutParamsFlags(int flags) {
+        if (mHandle.layoutParamsFlags == flags) {
+            return;
+        }
+        mHandle.layoutParamsFlags = flags;
+        mChanged = true;
+    }
+
+    void setLayoutParamsType(int type) {
+        if (mHandle.layoutParamsType == type) {
+            return;
+        }
+        mHandle.layoutParamsType = type;
+        mChanged = true;
+    }
+
+    void setDispatchingTimeoutMillis(long timeout) {
+        if (mHandle.dispatchingTimeoutMillis == timeout) {
+            return;
+        }
+        mHandle.dispatchingTimeoutMillis = timeout;
+        mChanged = true;
+    }
+
+    void setTouchableRegion(Region region) {
+        if (mHandle.touchableRegion.equals(region)) {
+            return;
+        }
+        mHandle.touchableRegion.set(region);
+        mChanged = true;
+    }
+
+    void clearTouchableRegion() {
+        if (mHandle.touchableRegion.isEmpty()) {
+            return;
+        }
+        mHandle.touchableRegion.setEmpty();
+        mChanged = true;
+    }
+
+    void setVisible(boolean visible) {
+        if (mHandle.visible == visible) {
+            return;
+        }
+        mHandle.visible = visible;
+        mChanged = true;
+    }
+
+    void setFocusable(boolean focusable) {
+        if (mHandle.focusable == focusable) {
+            return;
+        }
+        mHandle.focusable = focusable;
+        mChanged = true;
+    }
+
+    void setTouchOcclusionMode(int mode) {
+        if (mHandle.touchOcclusionMode == mode) {
+            return;
+        }
+        mHandle.touchOcclusionMode = mode;
+        mChanged = true;
+    }
+
+    void setHasWallpaper(boolean hasWallpaper) {
+        if (mHandle.hasWallpaper == hasWallpaper) {
+            return;
+        }
+        mHandle.hasWallpaper = hasWallpaper;
+        mChanged = true;
+    }
+
+    void setPaused(boolean paused) {
+        if (mHandle.paused == paused) {
+            return;
+        }
+        mHandle.paused = paused;
+        mChanged = true;
+    }
+
+    void setTrustedOverlay(boolean trustedOverlay) {
+        if (mHandle.trustedOverlay == trustedOverlay) {
+            return;
+        }
+        mHandle.trustedOverlay = trustedOverlay;
+        mChanged = true;
+    }
+
+    void setOwnerPid(int pid) {
+        if (mHandle.ownerPid == pid) {
+            return;
+        }
+        mHandle.ownerPid = pid;
+        mChanged = true;
+    }
+
+    void setOwnerUid(int uid) {
+        if (mHandle.ownerUid == uid) {
+            return;
+        }
+        mHandle.ownerUid = uid;
+        mChanged = true;
+    }
+
+    void setPackageName(String packageName) {
+        if (Objects.equals(mHandle.packageName, packageName)) {
+            return;
+        }
+        mHandle.packageName = packageName;
+        mChanged = true;
+    }
+
+    void setInputFeatures(int features) {
+        if (mHandle.inputFeatures == features) {
+            return;
+        }
+        mHandle.inputFeatures = features;
+        mChanged = true;
+    }
+
+    void setDisplayId(int displayId) {
+        if (mHandle.displayId == displayId) {
+            return;
+        }
+        mHandle.displayId = displayId;
+        mChanged = true;
+    }
+
+    void setPortalToDisplayId(int displayId) {
+        if (mHandle.portalToDisplayId == displayId) {
+            return;
+        }
+        mHandle.portalToDisplayId = displayId;
+        mChanged = true;
+    }
+
+    void setFrame(int left, int top, int right, int bottom) {
+        if (mHandle.frameLeft == left && mHandle.frameTop == top && mHandle.frameRight == right
+                && mHandle.frameBottom == bottom) {
+            return;
+        }
+        mHandle.frameLeft = left;
+        mHandle.frameTop = top;
+        mHandle.frameRight = right;
+        mHandle.frameBottom = bottom;
+        mChanged = true;
+    }
+
+    void setSurfaceInset(int inset) {
+        if (mHandle.surfaceInset == inset) {
+            return;
+        }
+        mHandle.surfaceInset = inset;
+        mChanged = true;
+    }
+
+    void setScaleFactor(float scale) {
+        if (mHandle.scaleFactor == scale) {
+            return;
+        }
+        mHandle.scaleFactor = scale;
+        mChanged = true;
+    }
+
+    void replaceTouchableRegionWithCrop(@Nullable SurfaceControl bounds) {
+        setTouchableRegionCrop(bounds);
+        setReplaceTouchableRegionWithCrop(true);
+    }
+
+    void setTouchableRegionCrop(@Nullable SurfaceControl bounds) {
+        if (mHandle.touchableRegionSurfaceControl.get() == bounds) {
+            return;
+        }
+        mHandle.setTouchableRegionCrop(bounds);
+        mChanged = true;
+    }
+
+    void setReplaceTouchableRegionWithCrop(boolean replace) {
+        if (mHandle.replaceTouchableRegionWithCrop == replace) {
+            return;
+        }
+        mHandle.replaceTouchableRegionWithCrop = replace;
+        mChanged = true;
+    }
+
+    @Override
+    public String toString() {
+        return mHandle + ", changed=" + mChanged;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index e7f140f..773bf54 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
@@ -35,6 +36,7 @@
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.app.WindowConfiguration.WindowingMode;
+import android.os.Trace;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
@@ -281,6 +283,7 @@
      * Called when a layout pass has occurred.
      */
     void onPostLayout() {
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ISC.onPostLayout");
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             mProviders.valueAt(i).onPostLayout();
         }
@@ -297,6 +300,7 @@
             }
         }
         winInsetsChanged.clear();
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     void onInsetsModified(InsetsControlTarget caller) {
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 79b88d8..de2164d 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -167,7 +167,8 @@
 
         if (keyguardChanged) {
             // Irrelevant to AOD.
-            dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */);
+            dismissMultiWindowModeForTaskIfNeeded(null /* currentTaskControllsingOcclusion */,
+                    false /* turningScreenOn */);
             setKeyguardGoingAway(false);
             if (keyguardShowing) {
                 mDismissalRequested = false;
@@ -369,7 +370,6 @@
                 mService.continueWindowLayout();
             }
         }
-        dismissMultiWindowModeForTaskIfNeeded(currentTaskControllingOcclusion);
     }
 
     /**
@@ -398,6 +398,21 @@
         }
     }
 
+    /**
+     * Called when somebody wants to turn screen on.
+     */
+    private void handleTurnScreenOn(int displayId) {
+        if (displayId != DEFAULT_DISPLAY) {
+            return;
+        }
+
+        mStackSupervisor.wakeUp("handleTurnScreenOn");
+        if (mKeyguardShowing && canDismissKeyguard()) {
+            mWindowManager.dismissKeyguard(null /* callback */, null /* message */);
+            mDismissalRequested = true;
+        }
+    }
+
     boolean isDisplayOccluded(int displayId) {
         return getDisplayState(displayId).mOccluded;
     }
@@ -433,14 +448,15 @@
     }
 
     private void dismissMultiWindowModeForTaskIfNeeded(
-            @Nullable Task currentTaskControllingOcclusion) {
+            @Nullable Task currentTaskControllingOcclusion, boolean turningScreenOn) {
+        // If turningScreenOn is true, it means that the visibility state has changed from
+        // currentTaskControllingOcclusion and we should update windowing mode.
         // TODO(b/113840485): Handle docked stack for individual display.
-        if (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY)) {
+        if (!turningScreenOn && (!mKeyguardShowing || !isDisplayOccluded(DEFAULT_DISPLAY))) {
             return;
         }
 
         // Dismiss split screen
-
         // The lock screen is currently showing, but is occluded by a window that can
         // show on top of the lock screen. In this can we want to dismiss the docked
         // stack since it will be complicated/risky to try to put the activity on top
@@ -579,17 +595,26 @@
                     && controller.mWindowManager.isKeyguardSecure(
                     controller.mService.getCurrentUserId());
 
+            boolean occludingChange = false;
+            boolean turningScreenOn = false;
             if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
                     && mTopTurnScreenOnActivity != null
                     && !mService.mWindowManager.mPowerManager.isInteractive()
-                    && (mRequestDismissKeyguard || occludedByActivity)) {
-                controller.mStackSupervisor.wakeUp("handleTurnScreenOn");
+                    && (mRequestDismissKeyguard || occludedByActivity
+                        || controller.canDismissKeyguard())) {
+                turningScreenOn = true;
+                controller.handleTurnScreenOn(mDisplayId);
                 mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
             }
 
             if (lastOccluded != mOccluded) {
+                occludingChange = true;
                 controller.handleOccludedChanged(mDisplayId, task);
             }
+
+            if (occludingChange || turningScreenOn) {
+                controller.dismissMultiWindowModeForTaskIfNeeded(task, turningScreenOn);
+            }
         }
 
         /**
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index b33c2f2..5b7b5a1 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -24,6 +24,8 @@
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.content.Intent.ACTION_CALL_EMERGENCY;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
@@ -33,11 +35,6 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_ALLOWLISTED;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_DONT_LOCK;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-import static com.android.server.wm.Task.LOCK_TASK_AUTH_PINNABLE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -129,6 +126,18 @@
     /** Tag used for disabling of keyguard */
     private static final String LOCK_TASK_TAG = "Lock-to-App";
 
+    /** Can't be put in lockTask mode. */
+    static final int LOCK_TASK_AUTH_DONT_LOCK = 0;
+    /** Can enter app pinning with user approval. Can never start over existing lockTask task. */
+    static final int LOCK_TASK_AUTH_PINNABLE = 1;
+    /** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */
+    static final int LOCK_TASK_AUTH_LAUNCHABLE = 2;
+    /** Can enter lockTask without user approval. Can start over existing lockTask task. */
+    static final int LOCK_TASK_AUTH_ALLOWLISTED = 3;
+    /** Priv-app that starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing
+     * lockTask task. */
+    static final int LOCK_TASK_AUTH_LAUNCHABLE_PRIV = 4;
+
     private final IBinder mToken = new LockTaskToken();
     private final ActivityStackSupervisor mSupervisor;
     private final Context mContext;
@@ -265,11 +274,10 @@
     }
 
     /**
-     * @return whether the requested task is allowed to be locked (either allowlisted, or declares
-     * lockTaskMode="always" in the manifest).
+     * @return whether the requested task auth is allowed to be locked.
      */
-    boolean isTaskAllowlisted(Task task) {
-        switch(task.mLockTaskAuth) {
+    static boolean isTaskAuthAllowlisted(int lockTaskAuth) {
+        switch(lockTaskAuth) {
             case LOCK_TASK_AUTH_ALLOWLISTED:
             case LOCK_TASK_AUTH_LAUNCHABLE:
             case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
@@ -293,7 +301,30 @@
      * @return whether the requested task is disallowed to be launched.
      */
     boolean isLockTaskModeViolation(Task task, boolean isNewClearTask) {
-        if (isLockTaskModeViolationInternal(task, isNewClearTask)) {
+        // TODO: Double check what's going on here. If the task is already in lock task mode, it's
+        // likely allowlisted, so will return false below.
+        if (isTaskLocked(task) && !isNewClearTask) {
+            // If the task is already at the top and won't be cleared, then allow the operation
+        } else if (isLockTaskModeViolationInternal(task, task.mUserId, task.intent,
+                task.mLockTaskAuth)) {
+            showLockTaskToast();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @param activity an activity that is going to be started in a new task as the root activity.
+     * @return whether the given activity is allowed to be launched.
+     */
+    boolean isNewTaskLockTaskModeViolation(ActivityRecord activity) {
+        // Use the belong task (if any) to perform the lock task checks
+        if (activity.getTask() != null) {
+            return isLockTaskModeViolation(activity.getTask());
+        }
+
+        int auth = getLockTaskAuth(activity, null /* task */);
+        if (isLockTaskModeViolationInternal(activity, activity.mUserId, activity.intent, auth)) {
             showLockTaskToast();
             return true;
         }
@@ -310,25 +341,19 @@
         return mLockTaskModeTasks.get(0);
     }
 
-    private boolean isLockTaskModeViolationInternal(Task task, boolean isNewClearTask) {
-        // TODO: Double check what's going on here. If the task is already in lock task mode, it's
-        // likely allowlisted, so will return false below.
-        if (isTaskLocked(task) && !isNewClearTask) {
-            // If the task is already at the top and won't be cleared, then allow the operation
-            return false;
-        }
-
+    private boolean isLockTaskModeViolationInternal(WindowContainer wc, int userId,
+            Intent intent, int taskAuth) {
         // Allow recents activity if enabled by policy
-        if (task.isActivityTypeRecents() && isRecentsAllowed(task.mUserId)) {
+        if (wc.isActivityTypeRecents() && isRecentsAllowed(userId)) {
             return false;
         }
 
         // Allow emergency calling when the device is protected by a locked keyguard
-        if (isKeyguardAllowed(task.mUserId) && isEmergencyCallTask(task)) {
+        if (isKeyguardAllowed(userId) && isEmergencyCallIntent(intent)) {
             return false;
         }
 
-        return !(isTaskAllowlisted(task) || mLockTaskModeTasks.isEmpty());
+        return !(isTaskAuthAllowlisted(taskAuth) || mLockTaskModeTasks.isEmpty());
     }
 
     private boolean isRecentsAllowed(int userId) {
@@ -360,8 +385,7 @@
         return isPackageAllowlisted(userId, packageName);
     }
 
-    private boolean isEmergencyCallTask(Task task) {
-        final Intent intent = task.intent;
+    private boolean isEmergencyCallIntent(Intent intent) {
         if (intent == null) {
             return false;
         }
@@ -697,6 +721,40 @@
         }
     }
 
+    int getLockTaskAuth(@Nullable ActivityRecord rootActivity, @Nullable Task task) {
+        if (rootActivity == null && task == null) {
+            return LOCK_TASK_AUTH_DONT_LOCK;
+        }
+        if (rootActivity == null) {
+            return LOCK_TASK_AUTH_PINNABLE;
+        }
+
+        final String pkg = (task == null || task.realActivity == null) ? null
+                : task.realActivity.getPackageName();
+        final int userId = task != null ? task.mUserId : rootActivity.mUserId;
+        int lockTaskAuth = LOCK_TASK_AUTH_DONT_LOCK;
+        switch (rootActivity.lockTaskLaunchMode) {
+            case LOCK_TASK_LAUNCH_MODE_DEFAULT:
+                lockTaskAuth = isPackageAllowlisted(userId, pkg)
+                        ? LOCK_TASK_AUTH_ALLOWLISTED : LOCK_TASK_AUTH_PINNABLE;
+                break;
+
+            case LOCK_TASK_LAUNCH_MODE_NEVER:
+                lockTaskAuth = LOCK_TASK_AUTH_DONT_LOCK;
+                break;
+
+            case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+                lockTaskAuth = LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+                break;
+
+            case LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED:
+                lockTaskAuth = isPackageAllowlisted(userId, pkg)
+                        ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
+                break;
+        }
+        return lockTaskAuth;
+    }
+
     boolean isPackageAllowlisted(int userId, String pkg) {
         if (pkg == null) {
             return false;
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 37f9082..45cd359 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -658,8 +658,8 @@
         }
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final Task task = mTasks.get(i);
-            if (task.mUserId == userId
-                    && !mService.getLockTaskController().isTaskAllowlisted(task)) {
+            if (task.mUserId == userId && !mService.getLockTaskController().isTaskAuthAllowlisted(
+                    task.mLockTaskAuth)) {
                 remove(task);
             }
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 757c57f..2749cc9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -59,7 +59,7 @@
 import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList;
 import static com.android.server.wm.ActivityStackSupervisor.printThisActivity;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RECENTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
@@ -76,9 +76,9 @@
 import static com.android.server.wm.Task.ActivityState.RESUMED;
 import static com.android.server.wm.Task.ActivityState.STOPPED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
-import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
-import static com.android.server.wm.Task.STACK_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
+import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
+import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE;
@@ -232,20 +232,19 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
-            MATCH_TASK_IN_STACKS_ONLY,
-            MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
-            MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
+            MATCH_ATTACHED_TASK_ONLY,
+            MATCH_ATTACHED_TASK_OR_RECENT_TASKS,
+            MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE
     })
     public @interface AnyTaskForIdMatchTaskMode {
     }
 
-    // Match only tasks in the current stacks
-    static final int MATCH_TASK_IN_STACKS_ONLY = 0;
-    // Match either tasks in the current stacks, or in the recent tasks if not found in the stacks
-    static final int MATCH_TASK_IN_STACKS_OR_RECENT_TASKS = 1;
-    // Match either tasks in the current stacks, or in the recent tasks, restoring it to the
-    // provided stack id
-    static final int MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE = 2;
+    // Match only tasks that are attached to the hierarchy
+    static final int MATCH_ATTACHED_TASK_ONLY = 0;
+    // Match either attached tasks, or in the recent tasks if the tasks are detached
+    static final int MATCH_ATTACHED_TASK_OR_RECENT_TASKS = 1;
+    // Match either attached tasks, or in the recent tasks, restoring it to the provided task id
+    static final int MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE = 2;
 
     ActivityTaskManagerService mService;
     ActivityStackSupervisor mStackSupervisor;
@@ -1953,7 +1952,7 @@
 
                 for (int taskNdx = displayArea.getStackCount() - 1; taskNdx >= 0; --taskNdx) {
                     final Task rootTask = displayArea.getStackAt(taskNdx);
-                    if (rootTask.getVisibility(null /*starting*/) == STACK_VISIBILITY_INVISIBLE) {
+                    if (rootTask.getVisibility(null /*starting*/) == TASK_VISIBILITY_INVISIBLE) {
                         break;
                     }
 
@@ -2556,7 +2555,7 @@
 
     @Override
     public void onDisplayAdded(int displayId) {
-        if (DEBUG_STACK) Slog.v(TAG, "Display added displayId=" + displayId);
+        if (DEBUG_ROOT_TASK) Slog.v(TAG, "Display added displayId=" + displayId);
         synchronized (mService.mGlobalLock) {
             final DisplayContent display = getDisplayContentOrCreate(displayId);
             if (display == null) {
@@ -2578,7 +2577,7 @@
 
     @Override
     public void onDisplayRemoved(int displayId) {
-        if (DEBUG_STACK) Slog.v(TAG, "Display removed displayId=" + displayId);
+        if (DEBUG_ROOT_TASK) Slog.v(TAG, "Display removed displayId=" + displayId);
         if (displayId == DEFAULT_DISPLAY) {
             throw new IllegalArgumentException("Can't remove the primary display.");
         }
@@ -2595,7 +2594,7 @@
 
     @Override
     public void onDisplayChanged(int displayId) {
-        if (DEBUG_STACK) Slog.v(TAG, "Display changed displayId=" + displayId);
+        if (DEBUG_ROOT_TASK) Slog.v(TAG, "Display changed displayId=" + displayId);
         synchronized (mService.mGlobalLock) {
             final DisplayContent displayContent = getDisplayContent(displayId);
             if (displayContent != null) {
@@ -2888,7 +2887,7 @@
             // Temporarily set the task id to invalid in case in re-entry.
             options.setLaunchTaskId(INVALID_TASK_ID);
             final Task task = anyTaskForId(taskId,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options, onTop);
+                    MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, options, onTop);
             options.setLaunchTaskId(taskId);
             if (task != null) {
                 return task.getRootTask();
@@ -3444,7 +3443,7 @@
     }
 
     Task anyTaskForId(int id) {
-        return anyTaskForId(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE);
+        return anyTaskForId(id, MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE);
     }
 
     Task anyTaskForId(int id, @RootWindowContainer.AnyTaskForIdMatchTaskMode int matchMode) {
@@ -3462,7 +3461,7 @@
     Task anyTaskForId(int id, @RootWindowContainer.AnyTaskForIdMatchTaskMode int matchMode,
             @Nullable ActivityOptions aOptions, boolean onTop) {
         // If options are set, ensure that we are attempting to actually restore a task
-        if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
+        if (matchMode != MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
             throw new IllegalArgumentException("Should not specify activity options for non-restore"
                     + " lookup");
         }
@@ -3480,7 +3479,7 @@
                         getLaunchStack(null, aOptions, task, onTop);
                 if (launchStack != null && task.getRootTask() != launchStack) {
                     final int reparentMode = onTop
-                            ? REPARENT_MOVE_STACK_TO_FRONT : REPARENT_LEAVE_STACK_IN_PLACE;
+                            ? REPARENT_MOVE_ROOT_TASK_TO_FRONT : REPARENT_LEAVE_ROOT_TASK_IN_PLACE;
                     task.reparent(launchStack, onTop, reparentMode, ANIMATE, DEFER_RESUME,
                             "anyTaskForId");
                 }
@@ -3489,7 +3488,7 @@
         }
 
         // If we are matching stack tasks only, return now
-        if (matchMode == MATCH_TASK_IN_STACKS_ONLY) {
+        if (matchMode == MATCH_ATTACHED_TASK_ONLY) {
             return null;
         }
 
@@ -3506,11 +3505,11 @@
             return null;
         }
 
-        if (matchMode == MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) {
+        if (matchMode == MATCH_ATTACHED_TASK_OR_RECENT_TASKS) {
             return task;
         }
 
-        // Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
+        // Implicitly, this case is MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE
         if (!mStackSupervisor.restoreRecentTaskLocked(task, aOptions, onTop)) {
             if (DEBUG_RECENTS) {
                 Slog.w(TAG_RECENTS,
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 6fbd351..b46e796 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -24,6 +24,7 @@
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
 import static android.content.Intent.EXTRA_SHORTCUT_ID;
 import static android.content.Intent.EXTRA_TASK_ID;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -43,6 +44,7 @@
 import android.content.ClipDescription;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.ShortcutServiceInternal;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -288,7 +290,8 @@
     public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource,
             float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
         // Validate and resolve ClipDescription data before clearing the calling identity
-        validateAndResolveDragMimeTypeExtras(data, Binder.getCallingUid());
+        validateAndResolveDragMimeTypeExtras(data, Binder.getCallingUid(), Binder.getCallingPid(),
+                mPackageName);
         final long ident = Binder.clearCallingIdentity();
         try {
             return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
@@ -302,8 +305,9 @@
      * Validates the given drag data.
      */
     @VisibleForTesting
-    public void validateAndResolveDragMimeTypeExtras(ClipData data, int callingUid) {
-        if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+    void validateAndResolveDragMimeTypeExtras(ClipData data, int callingUid, int callingPid,
+            String callingPackage) {
+        if (callingUid == Process.SYSTEM_UID) {
             throw new IllegalStateException("Need to validate before calling identify is cleared");
         }
         final ClipDescription desc = data != null ? data.getDescription() : null;
@@ -358,26 +362,58 @@
                 Binder.restoreCallingIdentity(origId);
             }
         } else if (hasShortcut) {
+            // Restrict who can start a shortcut drag since it will start the shortcut as the
+            // target shortcut package
             mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
                     "performDrag");
             for (int i = 0; i < data.getItemCount(); i++) {
-                final Intent intent = data.getItemAt(i).getIntent();
+                final ClipData.Item item = data.getItemAt(i);
+                final Intent intent = item.getIntent();
+                final String shortcutId = intent.getStringExtra(EXTRA_SHORTCUT_ID);
+                final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
                 final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
-                if (!intent.hasExtra(EXTRA_SHORTCUT_ID)
-                        || TextUtils.isEmpty(intent.getStringExtra(EXTRA_SHORTCUT_ID))
+                if (TextUtils.isEmpty(shortcutId)
+                        || TextUtils.isEmpty(packageName)
                         || user == null) {
-                    throw new IllegalArgumentException("Clip item must include the shortcut id and "
-                            + "the user to launch for.");
+                    throw new IllegalArgumentException("Clip item must include the package name, "
+                            + "shortcut id, and the user to launch for.");
                 }
+                final ShortcutServiceInternal shortcutService =
+                        LocalServices.getService(ShortcutServiceInternal.class);
+                final Intent[] shortcutIntents = shortcutService.createShortcutIntents(
+                        callingUid, callingPackage, packageName, shortcutId,
+                        user.getIdentifier(), callingPid, callingUid);
+                if (shortcutIntents == null || shortcutIntents.length == 0) {
+                    throw new IllegalArgumentException("Invalid shortcut id");
+                }
+                final ActivityInfo info = mService.mAtmService.resolveActivityInfoForIntent(
+                        shortcutIntents[0], null /* resolvedType */, user.getIdentifier(),
+                        callingUid);
+                item.setActivityInfo(info);
             }
         } else if (hasTask) {
+            // TODO(b/169894807): Consider opening this up for tasks from the same app as the caller
             mService.mAtmService.enforceCallerIsRecentsOrHasPermission(START_TASKS_FROM_RECENTS,
                     "performDrag");
             for (int i = 0; i < data.getItemCount(); i++) {
-                final Intent intent = data.getItemAt(i).getIntent();
-                if (intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID) == INVALID_TASK_ID) {
+                final ClipData.Item item = data.getItemAt(i);
+                final Intent intent = item.getIntent();
+                final int taskId = intent.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
+                if (taskId == INVALID_TASK_ID) {
                     throw new IllegalArgumentException("Clip item must include the task id.");
                 }
+                final Task task = mService.mRoot.anyTaskForId(taskId);
+                if (task == null) {
+                    throw new IllegalArgumentException("Invalid task id.");
+                }
+                if (task.getRootActivity() != null) {
+                    item.setActivityInfo(task.getRootActivity().info);
+                } else {
+                    // Resolve the activity info manually if the task was restored after reboot
+                    final ActivityInfo info = mService.mAtmService.resolveActivityInfoForIntent(
+                            task.intent, null /* resolvedType */, task.mUserId, callingUid);
+                    item.setActivityInfo(info);
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9273bf7..cbb3c42 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -43,10 +43,6 @@
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
 import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
-import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
-import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED;
-import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
@@ -107,7 +103,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RECENTS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS;
-import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TASKS;
@@ -120,6 +116,11 @@
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
 import static com.android.server.wm.Task.ActivityState.PAUSED;
 import static com.android.server.wm.Task.ActivityState.PAUSING;
@@ -145,7 +146,7 @@
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.TASK;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO;
@@ -251,7 +252,7 @@
     static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
     private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE;
     private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS;
-    private static final String TAG_STACK = TAG + POSTFIX_STACK;
+    private static final String TAG_ROOT_TASK = TAG + POSTFIX_ROOT_TASK;
     private static final String TAG_STATES = TAG + POSTFIX_STATES;
     private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH;
     private static final String TAG_TRANSITION = TAG + POSTFIX_TRANSITION;
@@ -308,38 +309,37 @@
     private float mShadowRadius = 0;
 
     /**
-     * The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
+     * The modes to control how root task is moved to the front when calling {@link Task#reparent}.
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
-            REPARENT_MOVE_STACK_TO_FRONT,
-            REPARENT_KEEP_STACK_AT_FRONT,
-            REPARENT_LEAVE_STACK_IN_PLACE
+            REPARENT_MOVE_ROOT_TASK_TO_FRONT,
+            REPARENT_KEEP_ROOT_TASK_AT_FRONT,
+            REPARENT_LEAVE_ROOT_TASK_IN_PLACE
     })
-    @interface ReparentMoveStackMode {}
-    // Moves the stack to the front if it was not at the front
-    static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
-    // Only moves the stack to the front if it was focused or front most already
-    static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
-    // Do not move the stack as a part of reparenting
-    static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+    @interface ReparentMoveRootTaskMode {}
+    // Moves the root task to the front if it was not at the front
+    static final int REPARENT_MOVE_ROOT_TASK_TO_FRONT = 0;
+    // Only moves the root task to the front if it was focused or front most already
+    static final int REPARENT_KEEP_ROOT_TASK_AT_FRONT = 1;
+    // Do not move the root task as a part of reparenting
+    static final int REPARENT_LEAVE_ROOT_TASK_IN_PLACE = 2;
 
-    // TODO (b/157876447): switch to Task related name
-    @IntDef(prefix = {"STACK_VISIBILITY"}, value = {
-            STACK_VISIBILITY_VISIBLE,
-            STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
-            STACK_VISIBILITY_INVISIBLE,
+    @IntDef(prefix = {"TASK_VISIBILITY"}, value = {
+            TASK_VISIBILITY_VISIBLE,
+            TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+            TASK_VISIBILITY_INVISIBLE,
     })
-    @interface StackVisibility {}
+    @interface TaskVisibility {}
 
-    /** Stack is visible. No other stacks on top that fully or partially occlude it. */
-    static final int STACK_VISIBILITY_VISIBLE = 0;
+    /** Task is visible. No other tasks on top that fully or partially occlude it. */
+    static final int TASK_VISIBILITY_VISIBLE = 0;
 
-    /** Stack is partially occluded by other translucent stack(s) on top of it. */
-    static final int STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
+    /** Task is partially occluded by other translucent task(s) on top of it. */
+    static final int TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT = 1;
 
-    /** Stack is completely invisible. */
-    static final int STACK_VISIBILITY_INVISIBLE = 2;
+    /** Task is completely invisible. */
+    static final int TASK_VISIBILITY_INVISIBLE = 2;
 
     enum ActivityState {
         INITIALIZING,
@@ -407,17 +407,6 @@
     boolean mUserSetupComplete; // The user set-up is complete as of the last time the task activity
                                 // was changed.
 
-    /** Can't be put in lockTask mode. */
-    final static int LOCK_TASK_AUTH_DONT_LOCK = 0;
-    /** Can enter app pinning with user approval. Can never start over existing lockTask task. */
-    final static int LOCK_TASK_AUTH_PINNABLE = 1;
-    /** Starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing lockTask task. */
-    final static int LOCK_TASK_AUTH_LAUNCHABLE = 2;
-    /** Can enter lockTask without user approval. Can start over existing lockTask task. */
-    final static int LOCK_TASK_AUTH_ALLOWLISTED = 3;
-    /** Priv-app that starts in LOCK_TASK_MODE_LOCKED automatically. Can start over existing
-     * lockTask task. */
-    final static int LOCK_TASK_AUTH_LAUNCHABLE_PRIV = 4;
     int mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE;
 
     int mLockTaskUid = -1;  // The uid of the application that called startLockTask().
@@ -964,7 +953,7 @@
             mAtmService.getLockTaskController().clearLockedTask(this);
         }
         if (shouldDeferRemoval()) {
-            if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
+            if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
             return;
         }
         removeImmediately();
@@ -1056,7 +1045,7 @@
 
     /** Convenience method to reparent a task to the top or bottom position of the stack. */
     boolean reparent(Task preferredStack, boolean toTop,
-            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            @ReparentMoveRootTaskMode int moveStackMode, boolean animate, boolean deferResume,
             String reason) {
         return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate, deferResume,
                 true /* schedulePictureInPictureModeChange */, reason);
@@ -1067,7 +1056,7 @@
      * an option to skip scheduling the picture-in-picture mode change.
      */
     boolean reparent(Task preferredStack, boolean toTop,
-            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            @ReparentMoveRootTaskMode int moveStackMode, boolean animate, boolean deferResume,
             boolean schedulePictureInPictureModeChange, String reason) {
         return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate,
                 deferResume, schedulePictureInPictureModeChange, reason);
@@ -1075,7 +1064,7 @@
 
     /** Convenience method to reparent a task to a specific position of the stack. */
     boolean reparent(Task preferredStack, int position,
-            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            @ReparentMoveRootTaskMode int moveStackMode, boolean animate, boolean deferResume,
             String reason) {
         return reparent(preferredStack, position, moveStackMode, animate, deferResume,
                 true /* schedulePictureInPictureModeChange */, reason);
@@ -1101,7 +1090,7 @@
     // TODO: Inspect all call sites and change to just changing windowing mode of the stack vs.
     // re-parenting the task. Can only be done when we are no longer using static stack Ids.
     boolean reparent(Task preferredStack, int position,
-            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            @ReparentMoveRootTaskMode int moveStackMode, boolean animate, boolean deferResume,
             boolean schedulePictureInPictureModeChange, String reason) {
         final ActivityStackSupervisor supervisor = mStackSupervisor;
         final RootWindowContainer root = mRootWindowContainer;
@@ -1156,8 +1145,9 @@
             final boolean wasFront = r != null && sourceStack.isTopStackInDisplayArea()
                     && (sourceStack.topRunningActivity() == r);
 
-            final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
-                    || (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
+            final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_ROOT_TASK_TO_FRONT
+                    || (moveStackMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT
+                            && (wasFocused || wasFront));
 
             reparent(toStack, position, moveStackToFront, reason);
 
@@ -1181,7 +1171,7 @@
             toStack.prepareFreezingTaskBounds();
 
             if (toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
-                    && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
+                    && moveStackMode == REPARENT_KEEP_ROOT_TASK_AT_FRONT) {
                 // Move recents to front so it is not behind home stack when going into docked
                 // mode
                 mStackSupervisor.moveRecentsStackToFront(reason);
@@ -1532,7 +1522,7 @@
             return;
         }
 
-        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) Slog.d(TAG_STACK,
+        if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) Slog.d(TAG_ROOT_TASK,
                 "setResumedActivity stack:" + this + " + from: "
                 + mResumedActivity + " to:" + r + " reason:" + reason);
         mResumedActivity = r;
@@ -1941,32 +1931,7 @@
     }
 
     private void setLockTaskAuth(@Nullable ActivityRecord r) {
-        if (r == null) {
-            mLockTaskAuth = LOCK_TASK_AUTH_PINNABLE;
-            return;
-        }
-
-        final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
-        final LockTaskController lockTaskController = mAtmService.getLockTaskController();
-        switch (r.lockTaskLaunchMode) {
-            case LOCK_TASK_LAUNCH_MODE_DEFAULT:
-                mLockTaskAuth = lockTaskController.isPackageAllowlisted(mUserId, pkg)
-                        ? LOCK_TASK_AUTH_ALLOWLISTED : LOCK_TASK_AUTH_PINNABLE;
-                break;
-
-            case LOCK_TASK_LAUNCH_MODE_NEVER:
-                mLockTaskAuth = LOCK_TASK_AUTH_DONT_LOCK;
-                break;
-
-            case LOCK_TASK_LAUNCH_MODE_ALWAYS:
-                mLockTaskAuth = LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
-                break;
-
-            case LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED:
-                mLockTaskAuth = lockTaskController.isPackageAllowlisted(mUserId, pkg)
-                        ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
-                break;
-        }
+        mLockTaskAuth = mAtmService.getLockTaskController().getLockTaskAuth(r, this);
         ProtoLog.d(WM_DEBUG_LOCKTASK, "setLockTaskAuth: task=%s mLockTaskAuth=%s", this,
                 lockTaskAuthToString());
     }
@@ -2210,8 +2175,8 @@
         }
 
         if (state == RESUMED) {
-            if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
-                Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" + reason);
+            if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
+                Slog.v(TAG_ROOT_TASK, "set resumed activity to:" + record + " reason:" + reason);
             }
             setResumedActivity(record, reason + " - onActivityStateChanged");
             if (record == mRootWindowContainer.getTopResumedActivity()) {
@@ -3265,7 +3230,7 @@
 
     @Override
     void removeImmediately() {
-        if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
+        if (DEBUG_ROOT_TASK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
         EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
 
         // If applicable let the TaskOrganizer know the Task is vanishing.
@@ -3276,7 +3241,7 @@
 
     // TODO: Consolidate this with Task.reparent()
     void reparent(Task stack, int position, boolean moveParents, String reason) {
-        if (DEBUG_STACK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
+        if (DEBUG_ROOT_TASK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
                 + " from stack=" + getRootTask());
         EventLogTags.writeWmTaskRemoved(mTaskId, "reParentTask:" + reason);
 
@@ -4180,7 +4145,7 @@
      * @param starting The currently starting activity or null if there is none.
      */
     boolean shouldBeVisible(ActivityRecord starting) {
-        return getVisibility(starting) != STACK_VISIBILITY_INVISIBLE;
+        return getVisibility(starting) != TASK_VISIBILITY_INVISIBLE;
     }
 
     /**
@@ -4188,14 +4153,14 @@
      *
      * @param starting The currently starting activity or null if there is none.
      */
-    @Task.StackVisibility
+    @TaskVisibility
     int getVisibility(ActivityRecord starting) {
         if (!isAttached() || isForceHidden()) {
-            return STACK_VISIBILITY_INVISIBLE;
+            return TASK_VISIBILITY_INVISIBLE;
         }
 
         if (isTopActivityLaunchedBehind()) {
-            return STACK_VISIBILITY_VISIBLE;
+            return TASK_VISIBILITY_VISIBLE;
         }
 
         boolean gotSplitScreenStack = false;
@@ -4211,10 +4176,10 @@
         final WindowContainer parent = getParent();
         if (parent.asTask() != null) {
             final int parentVisibility = parent.asTask().getVisibility(starting);
-            if (parentVisibility == STACK_VISIBILITY_INVISIBLE) {
+            if (parentVisibility == TASK_VISIBILITY_INVISIBLE) {
                 // Can't be visible if parent isn't visible
-                return STACK_VISIBILITY_INVISIBLE;
-            } else if (parentVisibility == STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
+                return TASK_VISIBILITY_INVISIBLE;
+            } else if (parentVisibility == TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT) {
                 // Parent is behind a translucent container so the highest visibility this container
                 // can get is that.
                 gotTranslucentFullscreen = true;
@@ -4249,7 +4214,7 @@
                     gotTranslucentFullscreen = true;
                     continue;
                 }
-                return STACK_VISIBILITY_INVISIBLE;
+                return TASK_VISIBILITY_INVISIBLE;
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     && !gotOpaqueSplitScreenPrimary) {
                 gotSplitScreenStack = true;
@@ -4258,7 +4223,7 @@
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                         && gotOpaqueSplitScreenPrimary) {
                     // Can not be visible behind another opaque stack in split-screen-primary mode.
-                    return STACK_VISIBILITY_INVISIBLE;
+                    return TASK_VISIBILITY_INVISIBLE;
                 }
             } else if (otherWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                     && !gotOpaqueSplitScreenSecondary) {
@@ -4268,24 +4233,24 @@
                 if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
                         && gotOpaqueSplitScreenSecondary) {
                     // Can not be visible behind another opaque stack in split-screen-secondary mode.
-                    return STACK_VISIBILITY_INVISIBLE;
+                    return TASK_VISIBILITY_INVISIBLE;
                 }
             }
             if (gotOpaqueSplitScreenPrimary && gotOpaqueSplitScreenSecondary) {
                 // Can not be visible if we are in split-screen windowing mode and both halves of
                 // the screen are opaque.
-                return STACK_VISIBILITY_INVISIBLE;
+                return TASK_VISIBILITY_INVISIBLE;
             }
             if (isAssistantType && gotSplitScreenStack) {
                 // Assistant stack can't be visible behind split-screen. In addition to this not
                 // making sense, it also works around an issue here we boost the z-order of the
                 // assistant window surfaces in window manager whenever it is visible.
-                return STACK_VISIBILITY_INVISIBLE;
+                return TASK_VISIBILITY_INVISIBLE;
             }
         }
 
         if (!shouldBeVisible) {
-            return STACK_VISIBILITY_INVISIBLE;
+            return TASK_VISIBILITY_INVISIBLE;
         }
 
         // Handle cases when there can be a translucent split-screen stack on top.
@@ -4293,26 +4258,26 @@
             case WINDOWING_MODE_FULLSCREEN:
                 if (gotTranslucentSplitScreenPrimary || gotTranslucentSplitScreenSecondary) {
                     // At least one of the split-screen stacks that covers this one is translucent.
-                    return STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                    return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
                 }
                 break;
             case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
                 if (gotTranslucentSplitScreenPrimary) {
                     // Covered by translucent primary split-screen on top.
-                    return STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                    return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
                 }
                 break;
             case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
                 if (gotTranslucentSplitScreenSecondary) {
                     // Covered by translucent secondary split-screen on top.
-                    return STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+                    return TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
                 }
                 break;
         }
 
         // Lastly - check if there is a translucent fullscreen stack on top.
-        return gotTranslucentFullscreen ? STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
-                : STACK_VISIBILITY_VISIBLE;
+        return gotTranslucentFullscreen ? TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT
+                : TASK_VISIBILITY_VISIBLE;
     }
 
     private boolean isTopActivityLaunchedBehind() {
@@ -7326,7 +7291,7 @@
         boolean toTop = position >= getChildCount();
         boolean includingParents = toTop || getDisplayArea().getNextFocusableStack(this,
                 true /* ignoreCurrent */) == null;
-        if (WindowManagerDebugConfig.DEBUG_STACK) {
+        if (WindowManagerDebugConfig.DEBUG_ROOT_TASK) {
             Slog.i(TAG_WM, "positionChildAt: positioning task=" + task + " at " + position);
         }
         positionChildAt(position, task, includingParents);
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index e721319..bda5759 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -37,11 +37,11 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.server.wm.ActivityTaskManagerService.TAG_STACK;
+import static com.android.server.wm.ActivityTaskManagerService.TAG_ROOT_TASK;
 import static com.android.server.wm.DisplayContent.alwaysCreateStack;
 import static com.android.server.wm.Task.ActivityState.RESUMED;
-import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.Nullable;
@@ -311,7 +311,7 @@
 
     @Override
     void addChild(Task task, int position) {
-        if (DEBUG_STACK) Slog.d(TAG_WM, "Set task=" + task + " on taskDisplayArea=" + this);
+        if (DEBUG_ROOT_TASK) Slog.d(TAG_WM, "Set task=" + task + " on taskDisplayArea=" + this);
 
         addStackReferenceIfNeeded(task);
         position = findPositionForStack(position, task, true /* adding */);
@@ -831,8 +831,8 @@
     }
 
     void onStackRemoved(Task stack) {
-        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
-            Slog.v(TAG_STACK, "removeStack: detaching " + stack + " from displayId="
+        if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
+            Slog.v(TAG_ROOT_TASK, "removeStack: detaching " + stack + " from displayId="
                     + mDisplayContent.mDisplayId);
         }
         if (mPreferredTopFocusableStack == stack) {
@@ -870,7 +870,7 @@
             homeParentTask.positionChildAtBottom(task);
         } else {
             task.reparent(homeParentTask, false /* toTop */,
-                    Task.REPARENT_LEAVE_STACK_IN_PLACE, false /* animate */,
+                    Task.REPARENT_LEAVE_ROOT_TASK_IN_PLACE, false /* animate */,
                     false /* deferResume */, "positionTaskBehindHome");
         }
     }
@@ -1205,8 +1205,8 @@
             }
         }
         final Task currentFocusedStack = getFocusedStack();
-        if (ActivityTaskManagerDebugConfig.DEBUG_STACK) {
-            Slog.d(TAG_STACK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
+        if (ActivityTaskManagerDebugConfig.DEBUG_ROOT_TASK) {
+            Slog.d(TAG_ROOT_TASK, "allResumedActivitiesComplete: mLastFocusedStack changing from="
                     + mLastFocusedStack + " to=" + currentFocusedStack);
         }
         mLastFocusedStack = currentFocusedStack;
@@ -1230,7 +1230,7 @@
             final Task stack = getStackAt(stackNdx);
             final ActivityRecord resumedActivity = stack.getResumedActivity();
             if (resumedActivity != null
-                    && (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
+                    && (stack.getVisibility(resuming) != TASK_VISIBILITY_VISIBLE
                     || !stack.isTopActivityFocusable())) {
                 ProtoLog.d(WM_DEBUG_STATES, "pauseBackStacks: stack=%s "
                         + "mResumedActivity=%s", stack, resumedActivity);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index a1c5670..bd52b66 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -493,8 +493,7 @@
                         mTmpTaskInfo.positionInParent,
                         lastInfo.positionInParent)
                 || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams
-                || mTmpTaskInfo.getConfiguration().windowConfiguration.getWindowingMode()
-                        != lastInfo.getConfiguration().windowConfiguration.getWindowingMode()
+                || mTmpTaskInfo.getWindowingMode() != lastInfo.getWindowingMode()
                 || !TaskDescription.equals(mTmpTaskInfo.taskDescription, lastInfo.taskDescription);
         if (!changed) {
             int cfgChanges = mTmpTaskInfo.configuration.diff(lastInfo.configuration);
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index a3dc290..eff4b9e 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
 
 import android.annotation.NonNull;
 import android.graphics.Bitmap;
@@ -330,7 +330,7 @@
 
                                 final int taskId = task.mTaskId;
                                 if (mService.mRootWindowContainer.anyTaskForId(taskId,
-                                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
+                                        MATCH_ATTACHED_TASK_OR_RECENT_TASKS) != null) {
                                     // Should not happen.
                                     Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
                                 } else if (userId != task.mUserId) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index a6f0f46..614d221 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -225,10 +225,8 @@
                 mClientChannel, mService.mAnimationHandler.getLooper(),
                 mService.mAnimator.getChoreographer());
 
-        mDragApplicationHandle = new InputApplicationHandle(new Binder());
-        mDragApplicationHandle.name = TAG;
-        mDragApplicationHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
-
+        mDragApplicationHandle = new InputApplicationHandle(new Binder(), TAG,
+                DEFAULT_DISPATCHING_TIMEOUT_MILLIS);
 
         mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
                 displayContent.getDisplayId());
diff --git a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
index 93b0fd9..74337c2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
+++ b/services/core/java/com/android/server/wm/WindowManagerDebugConfig.java
@@ -50,7 +50,7 @@
     static final boolean DEBUG_WINDOW_TRACE = false;
     static final boolean DEBUG_TASK_MOVEMENT = false;
     static final boolean DEBUG_TASK_POSITIONING = false;
-    static final boolean DEBUG_STACK = false;
+    static final boolean DEBUG_ROOT_TASK = false;
     static final boolean DEBUG_DISPLAY = false;
     static final boolean DEBUG_POWER = false;
     static final boolean SHOW_VERBOSE_TRANSACTIONS = false;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 33b3e59..e7d9e6b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7697,6 +7697,7 @@
                 if (imeTarget == null) {
                     return;
                 }
+                Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0);
                 final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget();
                 imeTarget = controlTarget.getWindow();
                 // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which
@@ -7710,6 +7711,7 @@
 
         @Override
         public void hideIme(IBinder imeTargetWindowToken, int displayId) {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme");
             synchronized (mGlobalLock) {
                 WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
                 ProtoLog.d(WM_DEBUG_IME, "hideIme target: %s ", imeTarget);
@@ -7730,6 +7732,7 @@
                             WindowInsets.Type.ime(), true /* fromIme */);
                 }
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         @Override
@@ -8392,7 +8395,7 @@
                             embeddedWindow.getName());
                     return;
                 }
-                t.requestFocusTransfer(newFocusTarget.mInputWindowHandle.token, targetInputToken,
+                t.requestFocusTransfer(newFocusTarget.mInputChannelToken, targetInputToken,
                         displayId).apply();
                 EventLog.writeEvent(LOGTAG_INPUT_FOCUS,
                         "Transfer focus request " + newFocusTarget,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9234390..fc06461 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -556,9 +556,17 @@
     boolean mWindowRemovalAllowed;
 
     // Input channel and input window handle used by the input dispatcher.
-    final InputWindowHandle mInputWindowHandle;
+    final InputWindowHandleWrapper mInputWindowHandle;
     InputChannel mInputChannel;
 
+    /**
+     * The token will be assigned to {@link InputWindowHandle#token} if this window can receive
+     * input event. Note that the token of associated input window handle can be cleared if this
+     * window becomes unable to receive input, but this field will remain until the input channel
+     * is actually disposed.
+     */
+    IBinder mInputChannelToken;
+
     // Used to improve performance of toString()
     private String mStringNameCache;
     private CharSequence mLastTitle;
@@ -856,6 +864,21 @@
         DeathRecipient deathRecipient = new DeathRecipient();
         mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
+        mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
+                mActivityRecord != null
+                        ? mActivityRecord.getInputApplicationHandle(false /* update */) : null,
+                getDisplayId()));
+        mInputWindowHandle.setOwnerPid(s.mPid);
+        mInputWindowHandle.setOwnerUid(s.mUid);
+        mInputWindowHandle.setName(getName());
+        mInputWindowHandle.setPackageName(mAttrs.packageName);
+        mInputWindowHandle.setLayoutParamsType(mAttrs.type);
+        // Check private trusted overlay flag and window type to set trustedOverlay variable of
+        // input window handle.
+        mInputWindowHandle.setTrustedOverlay(
+                ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
+                        && mOwnerCanAddInternalSystemWindow)
+                        || InputMonitor.isTrustedOverlay(mAttrs.type));
         if (DEBUG) {
             Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
                             + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
@@ -871,7 +894,6 @@
             mIsFloatingLayer = false;
             mBaseLayer = 0;
             mSubLayer = 0;
-            mInputWindowHandle = null;
             mWinAnimator = null;
             mWpcForDisplayConfigChanges = null;
             return;
@@ -919,16 +941,6 @@
         mLastRequestedWidth = 0;
         mLastRequestedHeight = 0;
         mLayer = 0;
-        mInputWindowHandle = new InputWindowHandle(
-                mActivityRecord != null ? mActivityRecord.mInputApplicationHandle : null,
-                    getDisplayId());
-
-        //  Check private trusted overlay flag and window type to set trustedOverlay variable of
-        //  input window handle.
-        mInputWindowHandle.trustedOverlay =
-                (mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
-                && mOwnerCanAddInternalSystemWindow;
-        mInputWindowHandle.trustedOverlay |= InputMonitor.isTrustedOverlay(mAttrs.type);
 
         // Make sure we initial all fields before adding to parentWindow, to prevent exception
         // during onDisplayChanged.
@@ -1496,9 +1508,9 @@
         }
         super.onDisplayChanged(dc);
         // Window was not laid out for this display yet, so make sure mLayoutSeq does not match.
-        if (dc != null && mInputWindowHandle.displayId != dc.getDisplayId()) {
+        if (dc != null && mInputWindowHandle.getDisplayId() != dc.getDisplayId()) {
             mLayoutSeq = dc.mLayoutSeq - 1;
-            mInputWindowHandle.displayId = dc.getDisplayId();
+            mInputWindowHandle.setDisplayId(dc.getDisplayId());
         }
     }
 
@@ -2470,7 +2482,9 @@
         }
         String name = getName();
         mInputChannel = mWmService.mInputManager.createInputChannel(name);
-        mInputWindowHandle.token = mInputChannel.getToken();
+        mInputChannelToken = mInputChannel.getToken();
+        mInputWindowHandle.setToken(mInputChannelToken);
+        mWmService.mInputToWindowMap.put(mInputChannelToken, this);
         if (outInputChannel != null) {
             mInputChannel.copyTo(outInputChannel);
         } else {
@@ -2479,7 +2493,6 @@
             // Create fake event receiver that simply reports all events as handled.
             mDeadWindowEventReceiver = new DeadWindowEventReceiver(mInputChannel);
         }
-        mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
     }
 
     void disposeInputChannel() {
@@ -2487,17 +2500,19 @@
             mDeadWindowEventReceiver.dispose();
             mDeadWindowEventReceiver = null;
         }
+        if (mInputChannelToken != null) {
+            // Unregister server channel first otherwise it complains about broken channel.
+            mWmService.mInputManager.removeInputChannel(mInputChannelToken);
+            mWmService.mKeyInterceptionInfoForToken.remove(mInputChannelToken);
+            mWmService.mInputToWindowMap.remove(mInputChannelToken);
+            mInputChannelToken = null;
+        }
 
-        // unregister server channel first otherwise it complains about broken channel
         if (mInputChannel != null) {
-            mWmService.mInputManager.removeInputChannel(mInputChannel.getToken());
-
             mInputChannel.dispose();
             mInputChannel = null;
         }
-        mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
-        mWmService.mInputToWindowMap.remove(mInputWindowHandle.token);
-        mInputWindowHandle.token = null;
+        mInputWindowHandle.setToken(null);
     }
 
     /** Returns true if the replacement window was removed. */
@@ -2567,11 +2582,8 @@
         }
     }
 
-    int getSurfaceTouchableRegion(InputWindowHandle inputWindowHandle, int flags) {
+    int getSurfaceTouchableRegion(Region region, int flags) {
         final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
-        final Region region = inputWindowHandle.touchableRegion;
-        setTouchableRegionCropIfNeeded(inputWindowHandle);
-
         if (modal) {
             flags |= FLAG_NOT_TOUCH_MODAL;
             if (mActivityRecord != null) {
@@ -2593,7 +2605,10 @@
         }
 
         // Translate to surface based coordinates.
-        region.translate(-mWindowFrames.mFrame.left, -mWindowFrames.mFrame.top);
+        final Rect frame = mWindowFrames.mFrame;
+        if (frame.left != 0 || frame.top != 0) {
+            region.translate(-frame.left, -frame.top);
+        }
 
         // TODO(b/139804591): sizecompat layout needs to be reworked. Currently mFrame is post-
         // scaling but the existing logic doesn't expect that. The result is that the already-
@@ -3442,7 +3457,9 @@
                 break;
             case TOUCHABLE_INSETS_REGION: {
                 outRegion.set(mGivenTouchableRegion);
-                outRegion.translate(frame.left, frame.top);
+                if (frame.left != 0 || frame.top != 0) {
+                    outRegion.translate(frame.left, frame.top);
+                }
                 break;
             }
         }
@@ -3469,22 +3486,6 @@
         }
     }
 
-    private void setTouchableRegionCropIfNeeded(InputWindowHandle handle) {
-        final Task task = getTask();
-        if (task == null || !task.cropWindowsToStackBounds()) {
-            handle.setTouchableRegionCrop(null);
-            return;
-        }
-
-        final Task stack = task.getRootTask();
-        if (stack == null || inFreeformWindowingMode()) {
-            handle.setTouchableRegionCrop(null);
-            return;
-        }
-
-        handle.setTouchableRegionCrop(stack.getSurfaceControl());
-    }
-
     private void cropRegionToStackBoundsIfNeeded(Region region) {
         final Task task = getTask();
         if (task == null || !task.cropWindowsToStackBounds()) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d845b12..72aa766 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -488,6 +488,9 @@
             mSurfaceFormat = format;
 
             w.setHasSurface(true);
+            // The surface instance is changed. Make sure the input info can be applied to the
+            // new surface, e.g. relaunch activity.
+            w.mInputWindowHandle.forceChange();
 
             ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
                         "  CREATE SURFACE %s IN SESSION %s: pid=%d format=%d flags=0x%x / %s",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index c1d5f19..3a00196 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -650,7 +650,7 @@
 
         base::Result<std::shared_ptr<KeyCharacterMap>> ret =
                 KeyCharacterMap::loadContents(filenameChars.c_str(), contentsChars.c_str(),
-                                              KeyCharacterMap::FORMAT_OVERLAY);
+                                              KeyCharacterMap::Format::OVERLAY);
         if (ret) {
             result = *ret;
         }
diff --git a/services/core/xsd/cec-config/cec-config.xsd b/services/core/xsd/cec-config/cec-config.xsd
index 0801c88..ca02f70 100644
--- a/services/core/xsd/cec-config/cec-config.xsd
+++ b/services/core/xsd/cec-config/cec-config.xsd
@@ -12,6 +12,7 @@
   </xs:element>
   <xs:complexType name="setting">
     <xs:attribute name="name" type="xs:string"/>
+    <xs:attribute name="value-type" type="xs:string"/>
     <xs:attribute name="user-configurable" type="xs:boolean"/>
     <xs:element name="allowed-values" type="value-list" minOccurs="1" maxOccurs="1"/>
     <xs:element name="default-value" type="value" minOccurs="1" maxOccurs="1"/>
@@ -23,5 +24,6 @@
   </xs:complexType>
   <xs:complexType name="value">
     <xs:attribute name="string-value" type="xs:string"/>
+    <xs:attribute name="int-value" type="xs:int"/>
   </xs:complexType>
 </xs:schema>
diff --git a/services/core/xsd/cec-config/schema/current.txt b/services/core/xsd/cec-config/schema/current.txt
index 34faf45..00dd15b 100644
--- a/services/core/xsd/cec-config/schema/current.txt
+++ b/services/core/xsd/cec-config/schema/current.txt
@@ -12,15 +12,19 @@
     method public com.android.server.hdmi.cec.config.Value getDefaultValue();
     method public String getName();
     method public boolean getUserConfigurable();
+    method public String getValueType();
     method public void setAllowedValues(com.android.server.hdmi.cec.config.ValueList);
     method public void setDefaultValue(com.android.server.hdmi.cec.config.Value);
     method public void setName(String);
     method public void setUserConfigurable(boolean);
+    method public void setValueType(String);
   }
 
   public class Value {
     ctor public Value();
+    method public int getIntValue();
     method public String getStringValue();
+    method public void setIntValue(int);
     method public void setStringValue(String);
   }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 736a7be..2c92ae4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -27,9 +27,11 @@
 import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 
 import android.content.ContentResolver;
@@ -79,8 +81,11 @@
     private static final String CALLING_PACKAGE2 = "com.package.name2";
     private static final String NAMESPACE1 = "namespace1";
     private static final String NAMESPACE2 = "namespace2";
+    private static final String NAMESPACE3 = "namespace3";
     private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
             "persist.device_config.configuration.disable_rescue_party";
+    private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
+            "persist.device_config.configuration.disable_rescue_party_factory_reset";
 
     private MockitoSession mSession;
     private HashMap<String, String> mSystemSettingsMap;
@@ -183,27 +188,38 @@
 
     @Test
     public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
+        RescueParty.onSettingsProviderPublished(mMockContext);
+        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+                mMonitorCallbackCaptor.capture()));
+
         noteBoot();
 
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
+        // Record DeviceConfig accesses
+        RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
+        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
+
+        final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
+
         noteBoot();
 
-        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null);
+        verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedAllResetNamespaces);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
         noteBoot();
 
-        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null);
+        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
         noteBoot();
 
-        verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
         assertEquals(LEVEL_FACTORY_RESET,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
     }
@@ -230,7 +246,6 @@
 
         notePersistentAppCrash();
 
-        verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
         assertEquals(LEVEL_FACTORY_RESET,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
     }
@@ -247,6 +262,7 @@
         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
+        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
         // Fake DeviceConfig value changes
         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
         verify(mMockPackageWatchdog).startObservingHealth(observer,
@@ -255,10 +271,15 @@
         verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer),
                 mPackageListCaptor.capture(),
                 eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS));
+        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
+        verify(mMockPackageWatchdog).startObservingHealth(observer,
+                Arrays.asList(CALLING_PACKAGE2), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
         assertTrue(mPackageListCaptor.getValue().containsAll(
                 Arrays.asList(CALLING_PACKAGE1, CALLING_PACKAGE2)));
         // Perform and verify scoped resets
         final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
+        final String[] expectedAllResetNamespaces =
+                new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
         observer.execute(new VersionedPackage(
                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH);
         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces);
@@ -273,13 +294,12 @@
 
         observer.execute(new VersionedPackage(
                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
-        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/null);
+        verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces);
         assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
                 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
 
         observer.execute(new VersionedPackage(
                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH);
-        verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
         assertTrue(RescueParty.isAttemptingFactoryReset());
     }
 
@@ -288,7 +308,6 @@
         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
             noteBoot();
         }
-        verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
         assertTrue(RescueParty.isAttemptingFactoryReset());
     }
 
@@ -337,12 +356,25 @@
         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
 
-        // Restore the property value initalized in SetUp()
+        // Restore the property value initialized in SetUp()
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
     }
 
     @Test
+    public void testDisablingFactoryResetByDeviceConfigFlag() {
+        SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true));
+
+        for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+            noteBoot();
+        }
+        assertFalse(RescueParty.isAttemptingFactoryReset());
+
+        // Restore the property value initialized in SetUp()
+        SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
+    }
+
+    @Test
     public void testHealthCheckLevels() {
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
 
@@ -437,7 +469,7 @@
                 eq(resetMode), anyInt()));
         // Verify DeviceConfig resets
         if (resetNamespaces == null) {
-            verify(() -> DeviceConfig.resetToDefaults(resetMode, /*namespace=*/ null));
+            verify(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()), never());
         } else {
             for (String namespace : resetNamespaces) {
                 verify(() -> DeviceConfig.resetToDefaults(resetMode, namespace));
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index db4aba5..8e4942e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -47,6 +47,7 @@
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LAZY_BATCHING;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_LISTENER_TIMEOUT;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
 import static com.android.server.alarm.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
@@ -62,6 +63,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -365,7 +367,7 @@
     }
 
     private void setIdleUntilAlarm(int type, long triggerTime, PendingIntent pi) {
-        setTestAlarm(type, triggerTime, pi, 0, FLAG_IDLE_UNTIL, TEST_CALLING_UID);
+        setTestAlarm(type, triggerTime, pi, 0, FLAG_IDLE_UNTIL | FLAG_STANDALONE, TEST_CALLING_UID);
     }
 
     private void setWakeFromIdle(int type, long triggerTime, PendingIntent pi) {
@@ -410,6 +412,12 @@
         mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
     }
 
+    private void setDeviceConfigBoolean(String key, boolean val) {
+        mDeviceConfigKeys.add(key);
+        doReturn(val).when(mDeviceConfigProperties).getBoolean(eq(key), anyBoolean());
+        mService.mConstants.onPropertiesChanged(mDeviceConfigProperties);
+    }
+
     /**
      * Lowers quotas to make testing feasible. Careful while calling as this will replace any
      * existing settings for the calling test.
@@ -1382,6 +1390,35 @@
         }
     }
 
+    @Test
+    public void alarmStoreMigration() {
+        setDeviceConfigBoolean(KEY_LAZY_BATCHING, false);
+        final int numAlarms = 10;
+        final PendingIntent[] pis = new PendingIntent[numAlarms];
+        for (int i = 0; i < numAlarms; i++) {
+            pis[i] = getNewMockPendingIntent();
+            setTestAlarm(ELAPSED_REALTIME, mNowElapsedTest + i + 1, pis[i]);
+        }
+
+        final ArrayList<Alarm> alarmsBefore = mService.mAlarmStore.asList();
+        assertEquals(numAlarms, alarmsBefore.size());
+        for (int i = 0; i < numAlarms; i++) {
+            final PendingIntent pi = pis[i];
+            assertTrue(i + "th PendingIntent missing: ",
+                    alarmsBefore.removeIf(a -> a.matches(pi, null)));
+        }
+
+        setDeviceConfigBoolean(KEY_LAZY_BATCHING, true);
+
+        final ArrayList<Alarm> alarmsAfter = mService.mAlarmStore.asList();
+        assertEquals(numAlarms, alarmsAfter.size());
+        for (int i = 0; i < numAlarms; i++) {
+            final PendingIntent pi = pis[i];
+            assertTrue(i + "th PendingIntent missing: ",
+                    alarmsAfter.removeIf(a -> a.matches(pi, null)));
+        }
+    }
+
     @After
     public void tearDown() {
         if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
index c4fc61a..42fa3d4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmStoreTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.alarm;
 
+import static android.app.AlarmManager.ELAPSED_REALTIME;
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+
 import static com.android.server.alarm.Constants.TEST_CALLING_PACKAGE;
 import static com.android.server.alarm.Constants.TEST_CALLING_UID;
 
@@ -23,35 +26,50 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
 
 @Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class AlarmStoreTest {
-    private AlarmStore mAlarmStore;
 
-    @Before
-    public void setUp() {
-        mAlarmStore = new BatchingAlarmStore(null);
+    @Parameter
+    public AlarmStore mAlarmStore;
+
+    @Parameters
+    public static Object[] stores() {
+        return new AlarmStore[]{
+                new LazyAlarmStore(),
+                new BatchingAlarmStore(),
+        };
     }
 
     private static Alarm createAlarm(long whenElapsed, long windowLength) {
-        return createAlarm(AlarmManager.ELAPSED_REALTIME, whenElapsed, windowLength, 0);
+        return createAlarm(ELAPSED_REALTIME, whenElapsed, windowLength, 0);
     }
 
     private static Alarm createWakeupAlarm(long whenElapsed, long windowLength, int flags) {
-        return createAlarm(AlarmManager.ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength, flags);
+        return createAlarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, windowLength, flags);
+    }
+
+    private static Alarm createAlarmClock(long whenElapsed) {
+        final AlarmManager.AlarmClockInfo info = new AlarmManager.AlarmClockInfo(whenElapsed,
+                mock(PendingIntent.class));
+        return new Alarm(ELAPSED_REALTIME_WAKEUP, whenElapsed, whenElapsed, 0, 0,
+                mock(PendingIntent.class), null, null, null, 0, info, TEST_CALLING_UID,
+                TEST_CALLING_PACKAGE);
     }
 
     private static Alarm createAlarm(int type, long whenElapsed, long windowLength, int flags) {
@@ -206,4 +224,21 @@
         });
         assertEquals(7, mAlarmStore.getNextDeliveryTime());
     }
+
+    @Test
+    public void alarmClockRemovalListener() {
+        final Runnable onRemoved = mock(Runnable.class);
+        mAlarmStore.setAlarmClockRemovalListener(onRemoved);
+
+        final Alarm simpleAlarm = createAlarm(5, 0);
+        final Alarm alarmClock = createAlarmClock(10);
+
+        addAlarmsToStore(simpleAlarm, alarmClock);
+
+        mAlarmStore.remove(simpleAlarm::equals);
+        verifyZeroInteractions(onRemoved);
+
+        mAlarmStore.remove(alarmClock::equals);
+        verify(onRemoved).run();
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index 5c2b8ce..b955199 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -27,17 +27,18 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.view.SurfaceControl;
-import android.view.SurfaceControl.DisplayPrimaries;
 import android.view.SurfaceControl.CieXyz;
+import android.view.SurfaceControl.DisplayPrimaries;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -190,6 +191,7 @@
      * Matrix should match the precalculated one for given cct and display primaries.
      */
     @Test
+    @Ignore
     public void displayWhiteBalance_validateTransformMatrix() {
         DisplayPrimaries displayPrimaries = new DisplayPrimaries();
         displayPrimaries.red = new CieXyz();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
index ae9c618..a92357f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecConfigTest.java
@@ -17,6 +17,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
@@ -77,14 +79,16 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
-                + "      <value string-value=\"0\" />"
-                + "      <value string-value=\"1\" />"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
                 + "    </allowed-values>"
-                + "    <default-value string-value=\"1\" />"
+                + "    <default-value int-value=\"1\" />"
                 + "  </setting>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"false\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -123,14 +127,16 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
-                + "      <value string-value=\"0\" />"
-                + "      <value string-value=\"1\" />"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
                 + "    </allowed-values>"
-                + "    <default-value string-value=\"1\" />"
+                + "    <default-value int-value=\"1\" />"
                 + "  </setting>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -152,14 +158,16 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
-                + "      <value string-value=\"0\" />"
-                + "      <value string-value=\"1\" />"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
                 + "    </allowed-values>"
-                + "    <default-value string-value=\"1\" />"
+                + "    <default-value int-value=\"1\" />"
                 + "  </setting>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -172,6 +180,7 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"false\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -186,31 +195,32 @@
     }
 
     @Test
-    public void getAllowedValues_NoMasterXml() {
+    public void isStringValueType_NoMasterXml() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter, null, null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.getAllowedValues("foo"));
+                () -> hdmiCecConfig.isStringValueType("foo"));
     }
 
     @Test
-    public void getAllowedValues_InvalidSetting() {
+    public void isStringValueType_InvalidSetting() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "</cec-settings>", null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.getAllowedValues("foo"));
+                () -> hdmiCecConfig.isStringValueType("foo"));
     }
 
     @Test
-    public void getAllowedValues_BasicSanity() {
+    public void isStringValueType_BasicSanity() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -220,7 +230,107 @@
                 + "    <default-value string-value=\"to_tv\" />"
                 + "  </setting>"
                 + "</cec-settings>", null);
-        assertThat(hdmiCecConfig.getAllowedValues(
+        assertTrue(hdmiCecConfig.isStringValueType(
+                    HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP));
+    }
+
+    @Test
+    public void isIntValueType_NoMasterXml() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter, null, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.isIntValueType("foo"));
+    }
+
+    @Test
+    public void isIntValueType_InvalidSetting() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.isIntValueType("foo"));
+    }
+
+    @Test
+    public void isIntValueType_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertTrue(hdmiCecConfig.isIntValueType(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED));
+    }
+
+    @Test
+    public void getAllowedStringValues_NoMasterXml() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter, null, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getAllowedStringValues("foo"));
+    }
+
+    @Test
+    public void getAllowedStringValues_InvalidSetting() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getAllowedStringValues("foo"));
+    }
+
+    @Test
+    public void getAllowedStringValues_InvalidValueType() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getAllowedStringValues(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED));
+    }
+
+    @Test
+    public void getAllowedStringValues_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value string-value=\"to_tv\" />"
+                + "      <value string-value=\"broadcast\" />"
+                + "      <value string-value=\"none\" />"
+                + "    </allowed-values>"
+                + "    <default-value string-value=\"to_tv\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThat(hdmiCecConfig.getAllowedStringValues(
                     HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP))
                 .containsExactly(HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV,
                                  HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST,
@@ -228,31 +338,32 @@
     }
 
     @Test
-    public void getDefaultValue_NoMasterXml() {
+    public void getAllowedIntValues_NoMasterXml() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter, null, null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.getDefaultValue("foo"));
+                () -> hdmiCecConfig.getAllowedIntValues("foo"));
     }
 
     @Test
-    public void getDefaultValue_InvalidSetting() {
+    public void getAllowedIntValues_InvalidSetting() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "</cec-settings>", null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.getDefaultValue("foo"));
+                () -> hdmiCecConfig.getAllowedIntValues("foo"));
     }
 
     @Test
-    public void getDefaultValue_BasicSanity() {
+    public void getAllowedIntValues_InvalidValueType() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -262,32 +373,199 @@
                 + "    <default-value string-value=\"to_tv\" />"
                 + "  </setting>"
                 + "</cec-settings>", null);
-        assertThat(hdmiCecConfig.getDefaultValue(
-                    HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP))
-                .isEqualTo(HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getAllowedIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP));
     }
 
     @Test
-    public void getValue_NoMasterXml() {
+    public void getAllowedIntValues_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThat(hdmiCecConfig.getAllowedIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED))
+                .containsExactly(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED,
+                                 HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+    }
+
+    @Test
+    public void getDefaultStringValue_NoMasterXml() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter, null, null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.getValue("foo"));
+                () -> hdmiCecConfig.getDefaultStringValue("foo"));
     }
 
     @Test
-    public void getValue_InvalidSetting() {
+    public void getDefaultStringValue_InvalidSetting() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "</cec-settings>", null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.getValue("foo"));
+                () -> hdmiCecConfig.getDefaultStringValue("foo"));
     }
 
     @Test
-    public void getValue_GlobalSetting_BasicSanity() {
+    public void getDefaultStringValue_InvalidValueType() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getDefaultStringValue(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED));
+    }
+
+    @Test
+    public void getDefaultStringValue_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value string-value=\"to_tv\" />"
+                + "      <value string-value=\"broadcast\" />"
+                + "      <value string-value=\"none\" />"
+                + "    </allowed-values>"
+                + "    <default-value string-value=\"to_tv\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThat(hdmiCecConfig.getDefaultStringValue(
+                    HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP))
+                .isEqualTo(HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV);
+    }
+
+    @Test
+    public void getDefaultIntValue_NoMasterXml() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter, null, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getDefaultIntValue("foo"));
+    }
+
+    @Test
+    public void getDefaultIntValue_InvalidSetting() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getDefaultIntValue("foo"));
+    }
+
+    @Test
+    public void getDefaultIntValue_InvalidValueType() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value string-value=\"to_tv\" />"
+                + "      <value string-value=\"broadcast\" />"
+                + "      <value string-value=\"none\" />"
+                + "    </allowed-values>"
+                + "    <default-value string-value=\"to_tv\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getDefaultIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP));
+    }
+
+    @Test
+    public void getDefaultIntValue_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThat(hdmiCecConfig.getDefaultIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED))
+                .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+    }
+
+    @Test
+    public void getStringValue_NoMasterXml() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter, null, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getStringValue("foo"));
+    }
+
+    @Test
+    public void getStringValue_InvalidSetting() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getStringValue("foo"));
+    }
+
+    @Test
+    public void getStringValue_InvalidType() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getStringValue(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED));
+    }
+
+    @Test
+    public void getStringValue_GlobalSetting_BasicSanity() {
         when(mStorageAdapter.retrieveGlobalSetting(mContext,
                   Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
                   HdmiControlManager.SEND_STANDBY_ON_SLEEP_TO_TV))
@@ -297,6 +575,7 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -306,13 +585,13 @@
                 + "    <default-value string-value=\"to_tv\" />"
                 + "  </setting>"
                 + "</cec-settings>", null);
-        assertThat(hdmiCecConfig.getValue(
+        assertThat(hdmiCecConfig.getStringValue(
                     HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP))
                 .isEqualTo(HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
     }
 
     @Test
-    public void getValue_SystemProperty_BasicSanity() {
+    public void getStringValue_SystemProperty_BasicSanity() {
         when(mStorageAdapter.retrieveSystemProperty(
                   HdmiCecConfig.SYSPROP_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                   HdmiProperties.power_state_change_on_active_source_lost_values
@@ -324,6 +603,7 @@
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"power_state_change_on_active_source_lost\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"false\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"none\" />"
@@ -332,38 +612,130 @@
                 + "    <default-value string-value=\"none\" />"
                 + "  </setting>"
                 + "</cec-settings>", null);
-        assertThat(hdmiCecConfig.getValue(
+        assertThat(hdmiCecConfig.getStringValue(
                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST))
                 .isEqualTo(HdmiProperties.power_state_change_on_active_source_lost_values
                         .STANDBY_NOW.name().toLowerCase());
     }
 
     @Test
-    public void setValue_NoMasterXml() {
+    public void getIntValue_NoMasterXml() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter, null, null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.setValue("foo", "bar"));
+                () -> hdmiCecConfig.getIntValue("foo"));
     }
 
     @Test
-    public void setValue_InvalidSetting() {
+    public void getIntValue_InvalidSetting() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "</cec-settings>", null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.setValue("foo", "bar"));
+                () -> hdmiCecConfig.getIntValue("foo"));
     }
 
     @Test
-    public void setValue_NotConfigurable() {
+    public void getIntValue_InvalidType() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value string-value=\"to_tv\" />"
+                + "      <value string-value=\"broadcast\" />"
+                + "      <value string-value=\"none\" />"
+                + "    </allowed-values>"
+                + "    <default-value string-value=\"to_tv\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.getIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP));
+    }
+
+    @Test
+    public void getIntValue_GlobalSetting_BasicSanity() {
+        when(mStorageAdapter.retrieveGlobalSetting(mContext,
+                  Global.HDMI_CONTROL_ENABLED,
+                  Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED)))
+            .thenReturn(Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED));
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThat(hdmiCecConfig.getIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED))
+                .isEqualTo(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+    }
+
+    @Test
+    public void getIntValue_SystemProperty_BasicSanity() {
+        when(mStorageAdapter.retrieveSystemProperty(
+                  HdmiCecConfig.SYSPROP_SYSTEM_AUDIO_MODE_MUTING,
+                  Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED)))
+                .thenReturn(Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED));
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"system_audio_mode_muting\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThat(hdmiCecConfig.getIntValue(
+                    HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING))
+                .isEqualTo(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED);
+    }
+
+    @Test
+    public void setStringValue_NoMasterXml() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter, null, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.setStringValue("foo", "bar"));
+    }
+
+    @Test
+    public void setStringValue_InvalidSetting() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.setStringValue("foo", "bar"));
+    }
+
+    @Test
+    public void setStringValue_NotConfigurable() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"false\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -374,18 +746,19 @@
                 + "  </setting>"
                 + "</cec-settings>", null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.setValue(
+                () -> hdmiCecConfig.setStringValue(
                         HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
                         HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST));
     }
 
     @Test
-    public void setValue_InvalidValue() {
+    public void setStringValue_InvalidValue() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -396,18 +769,19 @@
                 + "  </setting>"
                 + "</cec-settings>", null);
         assertThrows(IllegalArgumentException.class,
-                () -> hdmiCecConfig.setValue(
+                () -> hdmiCecConfig.setStringValue(
                         HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
                         "bar"));
     }
 
     @Test
-    public void setValue_GlobalSetting_BasicSanity() {
+    public void setStringValue_GlobalSetting_BasicSanity() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"send_standby_on_sleep\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"to_tv\" />"
@@ -417,7 +791,7 @@
                 + "    <default-value string-value=\"to_tv\" />"
                 + "  </setting>"
                 + "</cec-settings>", null);
-        hdmiCecConfig.setValue(HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
+        hdmiCecConfig.setStringValue(HdmiControlManager.CEC_SETTING_NAME_SEND_STANDBY_ON_SLEEP,
                                HdmiControlManager.SEND_STANDBY_ON_SLEEP_BROADCAST);
         verify(mStorageAdapter).storeGlobalSetting(mContext,
                   Global.HDMI_CONTROL_SEND_STANDBY_ON_SLEEP,
@@ -425,12 +799,13 @@
     }
 
     @Test
-    public void setValue_SystemProperty_BasicSanity() {
+    public void setStringValue_SystemProperty_BasicSanity() {
         HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
                 mContext, mStorageAdapter,
                 "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
                 + "<cec-settings>"
                 + "  <setting name=\"power_state_change_on_active_source_lost\""
+                + "           value-type=\"string\""
                 + "           user-configurable=\"true\">"
                 + "    <allowed-values>"
                 + "      <value string-value=\"none\" />"
@@ -439,7 +814,7 @@
                 + "    <default-value string-value=\"none\" />"
                 + "  </setting>"
                 + "</cec-settings>", null);
-        hdmiCecConfig.setValue(
+        hdmiCecConfig.setStringValue(
                   HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
                   HdmiProperties.power_state_change_on_active_source_lost_values
                       .STANDBY_NOW.name().toLowerCase());
@@ -448,4 +823,114 @@
                   HdmiProperties.power_state_change_on_active_source_lost_values
                       .STANDBY_NOW.name().toLowerCase());
     }
+
+    @Test
+    public void setIntValue_NoMasterXml() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter, null, null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.setIntValue("foo", 0));
+    }
+
+    @Test
+    public void setIntValue_InvalidSetting() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.setIntValue("foo", 0));
+    }
+
+    @Test
+    public void setIntValue_NotConfigurable() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"false\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.setIntValue(
+                        HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                        HdmiControlManager.HDMI_CEC_CONTROL_DISABLED));
+    }
+
+    @Test
+    public void setIntValue_InvalidValue() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        assertThrows(IllegalArgumentException.class,
+                () -> hdmiCecConfig.setIntValue(
+                        HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                        123));
+    }
+
+    @Test
+    public void setIntValue_GlobalSetting_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"hdmi_cec_enabled\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        hdmiCecConfig.setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                                  HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        verify(mStorageAdapter).storeGlobalSetting(mContext,
+                  Global.HDMI_CONTROL_ENABLED,
+                  Integer.toString(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED));
+    }
+
+    @Test
+    public void setIntValue_SystemProperty_BasicSanity() {
+        HdmiCecConfig hdmiCecConfig = HdmiCecConfig.createFromStrings(
+                mContext, mStorageAdapter,
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+                + "<cec-settings>"
+                + "  <setting name=\"system_audio_mode_muting\""
+                + "           value-type=\"int\""
+                + "           user-configurable=\"true\">"
+                + "    <allowed-values>"
+                + "      <value int-value=\"0\" />"
+                + "      <value int-value=\"1\" />"
+                + "    </allowed-values>"
+                + "    <default-value int-value=\"1\" />"
+                + "  </setting>"
+                + "</cec-settings>", null);
+        hdmiCecConfig.setIntValue(
+                  HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
+                  HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED);
+        verify(mStorageAdapter).storeSystemProperty(
+                  HdmiCecConfig.SYSPROP_SYSTEM_AUDIO_MODE_MUTING,
+                  Integer.toString(HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_DISABLED));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
index 40d959d..9ba0967 100644
--- a/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/IncrementalStatesTest.java
@@ -44,7 +44,6 @@
 public class IncrementalStatesTest {
     private IncrementalStates mIncrementalStates;
     private ConditionVariable mUnstartableCalled = new ConditionVariable();
-    private ConditionVariable mStartableCalled = new ConditionVariable();
     private ConditionVariable mFullyLoadedCalled = new ConditionVariable();
     private AtomicInteger mUnstartableReason = new AtomicInteger(0);
     private static final int WAIT_TIMEOUT_MILLIS = 1000; /* 1 second */
@@ -57,7 +56,6 @@
 
         @Override
         public void onPackageStartable() {
-            mStartableCalled.open();
         }
 
         @Override
@@ -77,24 +75,22 @@
         mIncrementalStates.setCallback(mCallback);
         mIncrementalStates.onCommit(true);
         // Test that package is now startable and loading
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
         assertTrue(mIncrementalStates.isStartable());
         assertTrue(mIncrementalStates.isLoading());
-        mStartableCalled.close();
         mUnstartableCalled.close();
         mFullyLoadedCalled.close();
     }
 
     /**
-     * Test that startable state changes to false when Incremental Storage is unhealthy.
+     * Test that the package is still startable when Incremental Storage is unhealthy.
      */
     @Test
     public void testStartableTransition_IncrementalStorageUnhealthy() {
         mIncrementalStates.onStorageHealthStatusChanged(
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
-        // Test that package is now unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
         assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN, mUnstartableReason.get());
     }
 
@@ -112,73 +108,34 @@
     }
 
     /**
-     * Test that the package becomes unstartable when health status indicate storage issues.
+     * Test that the package is still startable when health status indicate storage issues.
      */
     @Test
     public void testStartableTransition_IncrementalStorageBlocked() {
         mIncrementalStates.onStorageHealthStatusChanged(
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_STORAGE);
-        // Test that package is now unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_INSUFFICIENT_STORAGE,
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN,
                 mUnstartableReason.get());
     }
 
     /**
-     * Test that the package becomes unstartable when health status indicates transport issues.
+     * Test that the package is still startable when health status indicates transport issues.
      */
     @Test
     public void testStartableTransition_DataLoaderIntegrityError() {
         mIncrementalStates.onStorageHealthStatusChanged(
                 IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_TRANSPORT);
-        // Test that package is now unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        assertEquals(PackageManager.UNSTARTABLE_REASON_CONNECTION_ERROR,
+        // Test that package is still startable
+        assertFalse(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
+        assertTrue(mIncrementalStates.isStartable());
+        assertEquals(PackageManager.UNSTARTABLE_REASON_UNKNOWN,
                 mUnstartableReason.get());
     }
 
     /**
-     * Test that the package becomes unstartable when Incremental Storage is unhealthy, and it
-     * becomes startable again when Incremental Storage is healthy again.
-     */
-    @Test
-    public void testStartableTransition_IncrementalStorageUnhealthyBackToHealthy()
-            throws InterruptedException {
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
-        // Test that package is unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_OK);
-        // Test that package is now startable
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
-    }
-
-    /**
-     * Test that the package becomes unstartable when health status indicates transportation issue,
-     * and it becomes startable again when health status is ok again.
-     */
-    @Test
-    public void testStartableTransition_DataLoaderUnhealthyBackToHealthy()
-            throws InterruptedException {
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY_TRANSPORT);
-        // Test that package is unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-
-        mIncrementalStates.onStorageHealthStatusChanged(IStorageHealthListener.HEALTH_STATUS_OK);
-        // Test that package is now startable
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
-    }
-
-    /**
      * Test that when loading progress is 1, the package becomes fully loaded, and the change of
      * Incremental Storage health status does not affect the startable state.
      */
@@ -197,43 +154,11 @@
     }
 
     /**
-     * Test that when loading progress is 1, the package becomes fully loaded, and if the package
-     * was unstartable, it becomes startable.
-     */
-    @Test
-    public void testLoadingTransition_FullyLoadedWhenUnstartable() throws InterruptedException {
-        mIncrementalStates.onStorageHealthStatusChanged(
-                IStorageHealthListener.HEALTH_STATUS_UNHEALTHY);
-        // Test that package is unstartable
-        assertTrue(mUnstartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        // Test that package is still loading
-        assertTrue(mIncrementalStates.isLoading());
-
-        mIncrementalStates.setProgress(0.5f);
-        // Test that package is still unstartable
-        assertFalse(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isStartable());
-        mIncrementalStates.setProgress(1.0f);
-        // Test that package is now startable
-        assertTrue(mStartableCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertTrue(mIncrementalStates.isStartable());
-        // Test that package is now fully loaded
-        assertTrue(mFullyLoadedCalled.block(WAIT_TIMEOUT_MILLIS));
-        assertFalse(mIncrementalStates.isLoading());
-    }
-
-    /**
      * Test startability transitions if app crashes or anrs
      */
     @Test
     public void testStartableTransition_AppCrashOrAnr() {
         mIncrementalStates.onCrashOrAnr();
-        assertFalse(mIncrementalStates.isStartable());
-        mIncrementalStates.setProgress(1.0f);
-        assertTrue(mIncrementalStates.isStartable());
-        mIncrementalStates.onCrashOrAnr();
-        // Test that if fully loaded, app remains startable even if it has crashed
         assertTrue(mIncrementalStates.isStartable());
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 93666b4..cf7f741 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -59,9 +59,9 @@
 import static com.android.server.wm.Task.ActivityState.STARTED;
 import static com.android.server.wm.Task.ActivityState.STOPPED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
-import static com.android.server.wm.Task.STACK_VISIBILITY_INVISIBLE;
-import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -516,13 +516,13 @@
         mActivity.setState(Task.ActivityState.STOPPED, "Testing");
         spyOn(mStack);
 
-        doReturn(STACK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
+        doReturn(TASK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
         assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
 
-        doReturn(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(mStack).getVisibility(null);
+        doReturn(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT).when(mStack).getVisibility(null);
         assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
 
-        doReturn(STACK_VISIBILITY_INVISIBLE).when(mStack).getVisibility(null);
+        doReturn(TASK_VISIBILITY_INVISIBLE).when(mStack).getVisibility(null);
         assertEquals(false, mActivity.shouldResumeActivity(null /* activeActivity */));
     }
 
@@ -535,7 +535,7 @@
         mActivity.addResultLocked(topActivity, "resultWho", 0, 0, new Intent());
         topActivity.finishing = true;
 
-        doReturn(STACK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
+        doReturn(TASK_VISIBILITY_VISIBLE).when(mStack).getVisibility(null);
         assertEquals(true, mActivity.shouldResumeActivity(null /* activeActivity */));
         assertEquals(false, mActivity.shouldPauseActivity(null /*activeActivity */));
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index 3c5b9f9..faf4f52 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -43,10 +43,10 @@
 import static com.android.server.wm.Task.ActivityState.STOPPED;
 import static com.android.server.wm.Task.ActivityState.STOPPING;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
-import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
-import static com.android.server.wm.Task.STACK_VISIBILITY_INVISIBLE;
-import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE;
-import static com.android.server.wm.Task.STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
+import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
+import static com.android.server.wm.Task.TASK_VISIBILITY_INVISIBLE;
+import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
+import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 import static com.android.server.wm.TaskDisplayArea.getStackAbove;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -123,7 +123,7 @@
         final Task destStack = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
-        mTask.reparent(destStack, true /* toTop */, Task.REPARENT_KEEP_STACK_AT_FRONT,
+        mTask.reparent(destStack, true /* toTop */, Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT,
                 false /* animate */, true /* deferResume*/,
                 "testResumedActivityFromTaskReparenting");
 
@@ -140,7 +140,7 @@
 
         final Task destStack = mDefaultTaskDisplayArea.createStack(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
-        mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_STACK_TO_FRONT, false, false,
+        mTask.reparent(destStack, true /*toTop*/, REPARENT_MOVE_ROOT_TASK_TO_FRONT, false, false,
                 "testResumedActivityFromActivityReparenting");
 
         assertNull(mStack.getResumedActivity());
@@ -418,10 +418,10 @@
         assertFalse(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
-        assertEquals(STACK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 splitScreenPrimary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 splitScreenSecondary.getVisibility(null /* starting */));
 
         // Home stack should be visible if one of the halves of split-screen is translucent.
@@ -429,11 +429,11 @@
         assertTrue(homeStack.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 homeStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 splitScreenPrimary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 splitScreenSecondary.getVisibility(null /* starting */));
 
         final Task splitScreenSecondary2 =
@@ -444,9 +444,9 @@
         doReturn(false).when(splitScreenSecondary2).isTranslucent(any());
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(STACK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_VISIBILITY_INVISIBLE,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // First split-screen secondary should be visible behind another translucent split-screen
@@ -454,9 +454,9 @@
         doReturn(true).when(splitScreenSecondary2).isTranslucent(any());
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         final Task assistantStack = createStackForShouldBeVisibleTest(
@@ -469,13 +469,13 @@
         assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 assistantStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_VISIBILITY_INVISIBLE,
                 splitScreenPrimary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_VISIBILITY_INVISIBLE,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_VISIBILITY_INVISIBLE,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Split-screen stacks should be visible behind a translucent fullscreen stack.
@@ -484,13 +484,13 @@
         assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary.shouldBeVisible(null /* starting */));
         assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 assistantStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenPrimary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenSecondary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitScreenSecondary2.getVisibility(null /* starting */));
 
         // Assistant stack shouldn't be visible behind translucent split-screen stack,
@@ -505,25 +505,25 @@
             assertTrue(assistantStack.shouldBeVisible(null /* starting */));
             assertFalse(splitScreenPrimary.shouldBeVisible(null /* starting */));
             assertFalse(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-            assertEquals(STACK_VISIBILITY_VISIBLE,
+            assertEquals(TASK_VISIBILITY_VISIBLE,
                     assistantStack.getVisibility(null /* starting */));
-            assertEquals(STACK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_VISIBILITY_INVISIBLE,
                     splitScreenPrimary.getVisibility(null /* starting */));
-            assertEquals(STACK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_VISIBILITY_INVISIBLE,
                     splitScreenSecondary.getVisibility(null /* starting */));
-            assertEquals(STACK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_VISIBILITY_INVISIBLE,
                     splitScreenSecondary2.getVisibility(null /* starting */));
         } else {
             assertFalse(assistantStack.shouldBeVisible(null /* starting */));
             assertTrue(splitScreenPrimary.shouldBeVisible(null /* starting */));
             assertTrue(splitScreenSecondary2.shouldBeVisible(null /* starting */));
-            assertEquals(STACK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_VISIBILITY_INVISIBLE,
                     assistantStack.getVisibility(null /* starting */));
-            assertEquals(STACK_VISIBILITY_VISIBLE,
+            assertEquals(TASK_VISIBILITY_VISIBLE,
                     splitScreenPrimary.getVisibility(null /* starting */));
-            assertEquals(STACK_VISIBILITY_INVISIBLE,
+            assertEquals(TASK_VISIBILITY_INVISIBLE,
                     splitScreenSecondary.getVisibility(null /* starting */));
-            assertEquals(STACK_VISIBILITY_VISIBLE,
+            assertEquals(TASK_VISIBILITY_VISIBLE,
                     splitScreenSecondary2.getVisibility(null /* starting */));
         }
     }
@@ -548,33 +548,33 @@
         // Re-parent home to split secondary.
         homeStack.reparent(splitSecondary, POSITION_TOP);
         // Current tasks should be visible.
-        assertEquals(STACK_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE, splitPrimary.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE, splitSecondary.getVisibility(null /* starting */));
         // Home task should still be visible even though it is a child of another visible task.
-        assertEquals(STACK_VISIBILITY_VISIBLE, homeStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE, homeStack.getVisibility(null /* starting */));
 
 
         // Add fullscreen translucent task that partially occludes split tasks
         final Task translucentStack = createStandardStackForVisibilityTest(
                 WINDOWING_MODE_FULLSCREEN, true /* translucent */);
         // Fullscreen translucent task should be visible
-        assertEquals(STACK_VISIBILITY_VISIBLE, translucentStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE, translucentStack.getVisibility(null /* starting */));
         // Split tasks should be visible behind translucent
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitPrimary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 splitSecondary.getVisibility(null /* starting */));
         // Home task should be visible behind translucent since its parent is visible behind
         // translucent.
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 homeStack.getVisibility(null /* starting */));
 
 
         // Hide split-secondary
         splitSecondary.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, true /* set */);
         // Home split secondary and home task should be invisible.
-        assertEquals(STACK_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_INVISIBLE, splitSecondary.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_INVISIBLE, homeStack.getVisibility(null /* starting */));
     }
 
     @Test
@@ -586,9 +586,9 @@
                 createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         true /* translucent */);
 
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 bottomStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 translucentStack.getVisibility(null /* starting */));
     }
 
@@ -604,10 +604,10 @@
                 createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         false /* translucent */);
 
-        assertEquals(STACK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_INVISIBLE,
                 translucentStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
     }
 
     @Test
@@ -622,10 +622,10 @@
                 createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         true /* translucent */);
 
-        assertEquals(STACK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_INVISIBLE, bottomStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 opaqueStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 translucentStack.getVisibility(null /* starting */));
     }
 
@@ -638,9 +638,9 @@
                 createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         true /* translucent */);
 
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 bottomTranslucentStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 translucentStack.getVisibility(null /* starting */));
     }
 
@@ -653,9 +653,9 @@
                 createStandardStackForVisibilityTest(WINDOWING_MODE_FULLSCREEN,
                         false /* translucent */);
 
-        assertEquals(STACK_VISIBILITY_INVISIBLE,
+        assertEquals(TASK_VISIBILITY_INVISIBLE,
                 bottomTranslucentStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE, opaqueStack.getVisibility(null /* starting */));
     }
 
     @Test
@@ -669,15 +669,15 @@
         final Task pinnedStack = createStackForShouldBeVisibleTest(mDefaultTaskDisplayArea,
                 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, true /* onTop */);
 
-        assertEquals(STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
+        assertEquals(TASK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT,
                 bottomStack.getVisibility(null /* starting */));
-        assertEquals(STACK_VISIBILITY_VISIBLE,
+        assertEquals(TASK_VISIBILITY_VISIBLE,
                 translucentStack.getVisibility(null /* starting */));
         // Add an activity to the pinned stack so it isn't considered empty for visibility check.
         final ActivityRecord pinnedActivity = new ActivityBuilder(mAtm)
                 .setTask(pinnedStack)
                 .build();
-        assertEquals(STACK_VISIBILITY_VISIBLE, pinnedStack.getVisibility(null /* starting */));
+        assertEquals(TASK_VISIBILITY_VISIBLE, pinnedStack.getVisibility(null /* starting */));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 3bd8c27..a7ced1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -486,7 +486,7 @@
         final ActivityStarter starter = prepareStarter(0);
 
         final LockTaskController lockTaskController = mAtm.getLockTaskController();
-        doReturn(true).when(lockTaskController).isLockTaskModeViolation(any());
+        doReturn(true).when(lockTaskController).isNewTaskLockTaskModeViolation(any());
 
         final int result = starter.setReason("testTaskModeViolation").execute();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 5cb3ac1..5641fe2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -83,6 +83,8 @@
 public class DragDropControllerTests extends WindowTestsBase {
     private static final int TIMEOUT_MS = 3000;
     private static final int TEST_UID = 12345;
+    private static final int TEST_PID = 67890;
+    private static final String TEST_PACKAGE = "com.test.package";
 
     private TestDragDropController mTarget;
     private WindowState mWindow;
@@ -243,21 +245,16 @@
         });
         try {
             session.validateAndResolveDragMimeTypeExtras(
-                    createClipDataForActivity(null, null), 0);
-            fail("Expected failure without pending intent and user");
-        } catch (IllegalArgumentException e) {
-            // Expected failure
-        }
-        try {
-            session.validateAndResolveDragMimeTypeExtras(
-                    createClipDataForActivity(mock(PendingIntent.class), null), 0);
+                    createClipDataForActivity(mock(PendingIntent.class), null), TEST_UID, TEST_PID,
+                    TEST_PACKAGE);
             fail("Expected failure without user");
         } catch (IllegalArgumentException e) {
             // Expected failure
         }
         try {
             session.validateAndResolveDragMimeTypeExtras(
-                    createClipDataForActivity(null, mock(UserHandle.class)), 0);
+                    createClipDataForActivity(null, mock(UserHandle.class)), TEST_UID, TEST_PID,
+                    TEST_PACKAGE);
             fail("Expected failure without pending intent");
         } catch (IllegalArgumentException e) {
             // Expected failure
@@ -286,15 +283,48 @@
             public void onAnimatorScaleChanged(float scale) {}
         });
         try {
-            final ClipData clipData = new ClipData(
-                    new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_SHORTCUT }),
-                    new ClipData.Item(new Intent()));
-
-            session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID);
+            session.validateAndResolveDragMimeTypeExtras(
+                    createClipDataForShortcut(null, "test_shortcut_id", mock(UserHandle.class)),
+                    TEST_UID, TEST_PID, TEST_PACKAGE);
+            fail("Expected failure without package name");
+        } catch (IllegalArgumentException e) {
+            // Expected failure
+        }
+        try {
+            session.validateAndResolveDragMimeTypeExtras(
+                    createClipDataForShortcut("test_package", null, mock(UserHandle.class)),
+                    TEST_UID, TEST_PID, TEST_PACKAGE);
             fail("Expected failure without shortcut id");
         } catch (IllegalArgumentException e) {
             // Expected failure
         }
+        try {
+            session.validateAndResolveDragMimeTypeExtras(
+                    createClipDataForShortcut("test_package", "test_shortcut_id", null),
+                    TEST_UID, TEST_PID, TEST_PACKAGE);
+            fail("Expected failure without package name");
+        } catch (IllegalArgumentException e) {
+            // Expected failure
+        }
+    }
+
+    private ClipData createClipDataForShortcut(String packageName, String shortcutId,
+            UserHandle user) {
+        final Intent data = new Intent();
+        if (packageName != null) {
+            data.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+        }
+        if (shortcutId != null) {
+            data.putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutId);
+        }
+        if (user != null) {
+            data.putExtra(Intent.EXTRA_USER, user);
+        }
+        final ClipData clipData = new ClipData(
+                new ClipDescription("drag", new String[] {
+                        MIMETYPE_APPLICATION_SHORTCUT}),
+                new ClipData.Item(data));
+        return clipData;
     }
 
     @Test
@@ -308,7 +338,8 @@
                     new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_TASK }),
                     new ClipData.Item(new Intent()));
 
-            session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID);
+            session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID, TEST_PID,
+                    TEST_PACKAGE);
             fail("Expected failure without task id");
         } catch (IllegalArgumentException e) {
             // Expected failure
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 044f819..bf718a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -50,6 +50,11 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_ALLOWLISTED;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
+import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_PINNABLE;
 import static com.android.server.wm.LockTaskController.STATUS_BAR_MASK_LOCKED;
 import static com.android.server.wm.LockTaskController.STATUS_BAR_MASK_PINNED;
 
@@ -169,7 +174,7 @@
     @Test
     public void testStartLockTaskMode_once() throws Exception {
         // GIVEN a task record with allowlisted auth
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
 
         // WHEN calling setLockTaskMode for LOCKED mode without resuming
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
@@ -186,8 +191,8 @@
     @Test
     public void testStartLockTaskMode_twice() throws Exception {
         // GIVEN two task records with allowlisted auth
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
 
         // WHEN calling setLockTaskMode for LOCKED mode on both tasks
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
@@ -206,7 +211,7 @@
     @Test
     public void testStartLockTaskMode_pinningRequest() {
         // GIVEN a task record that is not allowlisted, i.e. with pinned auth
-        Task tr = getTask(Task.LOCK_TASK_AUTH_PINNABLE);
+        Task tr = getTask(LOCK_TASK_AUTH_PINNABLE);
 
         // WHEN calling startLockTaskMode
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
@@ -218,7 +223,7 @@
     @Test
     public void testStartLockTaskMode_pinnedBySystem() throws Exception {
         // GIVEN a task record with pinned auth
-        Task tr = getTask(Task.LOCK_TASK_AUTH_PINNABLE);
+        Task tr = getTask(LOCK_TASK_AUTH_PINNABLE);
 
         // WHEN the system calls startLockTaskMode
         mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID);
@@ -237,41 +242,39 @@
     @Test
     public void testLockTaskViolation() {
         // GIVEN one task record with allowlisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN it's not a lock task violation to try and launch this task without clearing
         assertFalse(mLockTaskController.isLockTaskModeViolation(tr, false));
 
         // THEN it's a lock task violation to launch another task that is not allowlisted
-        assertTrue(mLockTaskController.isLockTaskModeViolation(getTask(
-                Task.LOCK_TASK_AUTH_PINNABLE)));
+        assertTrue(mLockTaskController.isLockTaskModeViolation(getTask(LOCK_TASK_AUTH_PINNABLE)));
         // THEN it's a lock task violation to launch another task that is disallowed from lock task
-        assertTrue(mLockTaskController.isLockTaskModeViolation(getTask(
-                Task.LOCK_TASK_AUTH_DONT_LOCK)));
+        assertTrue(mLockTaskController.isLockTaskModeViolation(getTask(LOCK_TASK_AUTH_DONT_LOCK)));
 
         // THEN it's no a lock task violation to launch another task that is allowlisted
         assertFalse(mLockTaskController.isLockTaskModeViolation(getTask(
-                Task.LOCK_TASK_AUTH_ALLOWLISTED)));
+                LOCK_TASK_AUTH_ALLOWLISTED)));
         assertFalse(mLockTaskController.isLockTaskModeViolation(getTask(
-                Task.LOCK_TASK_AUTH_LAUNCHABLE)));
+                LOCK_TASK_AUTH_LAUNCHABLE)));
         // THEN it's not a lock task violation to launch another task that is priv launchable
         assertFalse(mLockTaskController.isLockTaskModeViolation(getTask(
-                Task.LOCK_TASK_AUTH_LAUNCHABLE_PRIV)));
+                LOCK_TASK_AUTH_LAUNCHABLE_PRIV)));
     }
 
     @Test
     public void testLockTaskViolation_emergencyCall() {
         // GIVEN one task record with allowlisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // GIVEN tasks necessary for emergency calling
         Task keypad = getTask(new Intent().setComponent(EMERGENCY_DIALER_COMPONENT),
-                Task.LOCK_TASK_AUTH_PINNABLE);
+                LOCK_TASK_AUTH_PINNABLE);
         Task callAction = getTask(new Intent(Intent.ACTION_CALL_EMERGENCY),
-                Task.LOCK_TASK_AUTH_PINNABLE);
-        Task dialer = getTask("com.example.dialer", Task.LOCK_TASK_AUTH_PINNABLE);
+                LOCK_TASK_AUTH_PINNABLE);
+        Task dialer = getTask("com.example.dialer", LOCK_TASK_AUTH_PINNABLE);
         when(mTelecomManager.getSystemDialerPackage())
                 .thenReturn(dialer.intent.getComponent().getPackageName());
 
@@ -295,7 +298,7 @@
     @Test
     public void testStopLockTaskMode() throws Exception {
         // GIVEN one task record with allowlisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN the same caller calls stopLockTaskMode
@@ -312,7 +315,7 @@
     @Test(expected = SecurityException.class)
     public void testStopLockTaskMode_differentCaller() {
         // GIVEN one task record with allowlisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN a different caller calls stopLockTaskMode
@@ -324,7 +327,7 @@
     @Test
     public void testStopLockTaskMode_systemCaller() {
         // GIVEN one task record with allowlisted auth that is in lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN system calls stopLockTaskMode
@@ -337,8 +340,8 @@
     @Test
     public void testStopLockTaskMode_twoTasks() throws Exception {
         // GIVEN two task records with allowlisted auth that is in lock task mode
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
@@ -358,8 +361,8 @@
     @Test
     public void testStopLockTaskMode_rootTask() throws Exception {
         // GIVEN two task records with allowlisted auth that is in lock task mode
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
@@ -379,7 +382,7 @@
     @Test
     public void testStopLockTaskMode_pinned() throws Exception {
         // GIVEN one task records that is in pinned mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_PINNABLE);
+        Task tr = getTask(LOCK_TASK_AUTH_PINNABLE);
         mLockTaskController.startLockTaskMode(tr, true, SYSTEM_UID);
         // GIVEN that the keyguard is required to show after unlocking
         Settings.Secure.putInt(mContext.getContentResolver(),
@@ -406,8 +409,8 @@
     @Test
     public void testClearLockedTasks() throws Exception {
         // GIVEN two task records with allowlisted auth that is in lock task mode
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
-        Task tr2 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr2 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, false, TEST_UID);
         mLockTaskController.startLockTaskMode(tr2, false, TEST_UID);
 
@@ -434,7 +437,7 @@
                 .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -454,7 +457,7 @@
                 .thenReturn(true);
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -471,7 +474,7 @@
                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 1, mContext.getUserId());
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -488,7 +491,7 @@
                 Settings.Secure.LOCK_TO_APP_EXIT_LOCKED, 0, mContext.getUserId());
 
         // AND there is a task record
-        Task tr1 = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr1 = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr1, true, TEST_UID);
 
         // WHEN calling clearLockedTasks on the root task
@@ -574,7 +577,7 @@
     @Test
     public void testUpdateLockTaskFeatures() throws Exception {
         // GIVEN a locked task
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN lock task mode should be started with default status bar masks
@@ -616,7 +619,7 @@
     @Test
     public void testUpdateLockTaskFeatures_differentUser() throws Exception {
         // GIVEN a locked task
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN lock task mode should be started with default status bar masks
@@ -638,7 +641,7 @@
     @Test
     public void testUpdateLockTaskFeatures_keyguard() {
         // GIVEN a locked task
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // THEN keyguard should be disabled
@@ -704,7 +707,7 @@
                 TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
 
         // Start lock task mode
-        Task tr = getTask(Task.LOCK_TASK_AUTH_ALLOWLISTED);
+        Task tr = getTask(LOCK_TASK_AUTH_ALLOWLISTED);
         mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
 
         // WHEN LOCK_TASK_FEATURE_BLOCK_ACTIVITY_START_IN_TASK is not enabled
@@ -758,14 +761,13 @@
      * @param isAppAware {@code true} if the app has marked if allowlisted in its manifest
      */
     private Task getTaskForUpdate(String pkg, boolean isAppAware) {
-        final int authIfAllowlisted = isAppAware
-                ? Task.LOCK_TASK_AUTH_LAUNCHABLE
-                : Task.LOCK_TASK_AUTH_ALLOWLISTED;
+        final int authIfAllowlisted =
+                isAppAware ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_ALLOWLISTED;
         Task tr = getTask(pkg, authIfAllowlisted);
         doAnswer((invocation) -> {
             boolean isAllowlisted =
                     mLockTaskController.isPackageAllowlisted(TEST_USER_ID, pkg);
-            tr.mLockTaskAuth = isAllowlisted ? authIfAllowlisted : Task.LOCK_TASK_AUTH_PINNABLE;
+            tr.mLockTaskAuth = isAllowlisted ? authIfAllowlisted : LOCK_TASK_AUTH_PINNABLE;
             return null;
         }).when(tr).setLockTaskAuth();
         return tr;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 1879e9e..4b8bbc1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -38,7 +38,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.ActivityStackSupervisor.ON_TOP;
-import static com.android.server.wm.RootWindowContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE;
+import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
 import static com.android.server.wm.Task.ActivityState.STOPPED;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 
@@ -107,7 +107,7 @@
     public void testRestoringInvalidTask() {
         mRootWindowContainer.getDefaultDisplay().removeAllTasks();
         Task task = mRootWindowContainer.anyTaskForId(0 /*taskId*/,
-                MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
+                MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE, null, false /* onTop */);
         assertNull(task);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 9304dc5..5c4563e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -62,15 +62,18 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.when;
 
 import android.graphics.Insets;
 import android.graphics.Matrix;
 import android.graphics.Rect;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.util.Size;
 import android.view.DisplayCutout;
+import android.view.InputWindowHandle;
 import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -464,10 +467,10 @@
     public void testDisplayIdUpdatedOnReparent() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         // fake a different display
-        app.mInputWindowHandle.displayId = mDisplayContent.getDisplayId() + 1;
+        app.mInputWindowHandle.setDisplayId(mDisplayContent.getDisplayId() + 1);
         app.onDisplayChanged(mDisplayContent);
 
-        assertThat(app.mInputWindowHandle.displayId, is(mDisplayContent.getDisplayId()));
+        assertThat(app.mInputWindowHandle.getDisplayId(), is(mDisplayContent.getDisplayId()));
         assertThat(app.getDisplayId(), is(mDisplayContent.getDisplayId()));
     }
 
@@ -678,6 +681,54 @@
         assertFalse(win0.canReceiveTouchInput());
     }
 
+    @Test
+    public void testUpdateInputWindowHandle() {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+        win.mAttrs.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+        final InputWindowHandle handle = new InputWindowHandle(
+                win.mInputWindowHandle.getInputApplicationHandle(), win.getDisplayId());
+        final InputWindowHandleWrapper handleWrapper = new InputWindowHandleWrapper(handle);
+        final IBinder inputChannelToken = mock(IBinder.class);
+        win.mInputChannelToken = inputChannelToken;
+
+        mDisplayContent.getInputMonitor().populateInputWindowHandle(handleWrapper, win);
+
+        assertTrue(handleWrapper.isChanged());
+        assertEquals(inputChannelToken, handle.token);
+        assertEquals(win.mActivityRecord.getInputApplicationHandle(false /* update */),
+                handle.inputApplicationHandle);
+        assertEquals(win.mAttrs.inputFeatures, handle.inputFeatures);
+        assertEquals(win.isVisible(), handle.visible);
+
+        final SurfaceControl sc = mock(SurfaceControl.class);
+        final SurfaceControl.Transaction transaction = mSystemServicesTestRule.mTransaction;
+        InputMonitor.setInputWindowInfoIfNeeded(transaction, sc, handleWrapper);
+
+        // The fields of input window handle are changed, so it must set input window info
+        // successfully. And then the changed flag should be reset.
+        verify(transaction).setInputWindowInfo(eq(sc), eq(handle));
+        assertFalse(handleWrapper.isChanged());
+        // Populate the same states again, the handle should not detect change.
+        mDisplayContent.getInputMonitor().populateInputWindowHandle(handleWrapper, win);
+        assertFalse(handleWrapper.isChanged());
+
+        // Apply the no change handle, the invocation of setInputWindowInfo should be skipped.
+        clearInvocations(transaction);
+        InputMonitor.setInputWindowInfoIfNeeded(transaction, sc, handleWrapper);
+        verify(transaction, never()).setInputWindowInfo(any(), any());
+
+        // Populate as an overlay to disable the input of window.
+        InputMonitor.populateOverlayInputInfo(handleWrapper, false /* isVisible */);
+        // The overlay attributes should be set.
+        assertTrue(handleWrapper.isChanged());
+        assertFalse(handle.focusable);
+        assertFalse(handle.visible);
+        assertNull(handle.token);
+        assertEquals(0L, handle.dispatchingTimeoutMillis);
+        assertEquals(WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL,
+                handle.inputFeatures);
+    }
+
     @UseTestDisplay(addWindows = W_ACTIVITY)
     @Test
     public void testNeedsRelativeLayeringToIme_notAttached() {
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index a85eb53..1238e7b 100755
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1468,8 +1468,11 @@
 
         /**
          * Writes the string {@param input} into the outgoing text stream for this RTT call. Since
-         * RTT transmits text in real-time, this method should be called once for each character
-         * the user enters into the device.
+         * RTT transmits text in real-time, this method should be called once for each user action.
+         * For example, when the user enters text as discrete characters using the keyboard, this
+         * method should be called once for each character. However, if the user enters text by
+         * pasting or autocomplete, the entire contents of the pasted or autocompleted text should
+         * be sent in one call to this method.
          *
          * This method is not thread-safe -- calling it from multiple threads simultaneously may
          * lead to interleaved text.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 7f87019..83e63ef 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1365,6 +1365,7 @@
      * include those that were inserted before, maybe empty but not null.
      * @hide
      */
+    @NonNull
     @UnsupportedAppUsage
     public List<SubscriptionInfo> getAllSubscriptionInfoList() {
         if (VDBG) logd("[getAllSubscriptionInfoList]+");
@@ -1382,7 +1383,7 @@
         }
 
         if (result == null) {
-            result = new ArrayList<>();
+            result = Collections.emptyList();
         }
         return result;
     }
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index 3e2a6ee..b054dfc 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -240,6 +240,12 @@
      */
     @Deprecated
     public int getSuggestedRetryTime() {
+        // To match the pre-deprecated getSuggestedRetryTime() behavior.
+        if (mSuggestedRetryTime == RETRY_INTERVAL_UNDEFINED) {
+            return 0;
+        } else if (mSuggestedRetryTime > Integer.MAX_VALUE) {
+            return Integer.MAX_VALUE;
+        }
         return (int) mSuggestedRetryTime;
     }
 
diff --git a/telephony/java/android/telephony/ims/AudioCodecAttributes.aidl b/telephony/java/android/telephony/ims/AudioCodecAttributes.aidl
new file mode 100644
index 0000000..bbab548
--- /dev/null
+++ b/telephony/java/android/telephony/ims/AudioCodecAttributes.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.telephony.ims;
+
+parcelable AudioCodecAttributes;
diff --git a/telephony/java/android/telephony/ims/AudioCodecAttributes.java b/telephony/java/android/telephony/ims/AudioCodecAttributes.java
new file mode 100644
index 0000000..7b6ab00
--- /dev/null
+++ b/telephony/java/android/telephony/ims/AudioCodecAttributes.java
@@ -0,0 +1,131 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Range;
+
+/**
+ * Parcelable object to handle audio codec attributes.
+ * It provides the audio codec bitrate, bandwidth and their upper/lower bound.
+ *
+ * @hide
+ */
+@SystemApi
+public final class AudioCodecAttributes implements Parcelable {
+    // The audio codec bitrate in kbps.
+    private float mBitrateKbps;
+    // The range of the audio codec bitrate in kbps.
+    private Range<Float> mBitrateRangeKbps;
+    // The audio codec bandwidth in kHz.
+    private float mBandwidthKhz;
+    // The range of the audio codec bandwidth in kHz.
+    private Range<Float> mBandwidthRangeKhz;
+
+
+    /**
+     * Constructor.
+     *
+     * @param bitrateKbps        The audio codec bitrate in kbps.
+     * @param bitrateRangeKbps  The range of the audio codec bitrate in kbps.
+     * @param bandwidthKhz      The audio codec bandwidth in kHz.
+     * @param bandwidthRangeKhz The range of the audio codec bandwidth in kHz.
+     */
+
+    public AudioCodecAttributes(float bitrateKbps, @NonNull Range<Float> bitrateRangeKbps,
+            float bandwidthKhz, @NonNull Range<Float> bandwidthRangeKhz) {
+        mBitrateKbps = bitrateKbps;
+        mBitrateRangeKbps = bitrateRangeKbps;
+        mBandwidthKhz = bandwidthKhz;
+        mBandwidthRangeKhz = bandwidthRangeKhz;
+    }
+
+    private AudioCodecAttributes(Parcel in) {
+        mBitrateKbps = in.readFloat();
+        mBitrateRangeKbps = new Range<>(in.readFloat(), in.readFloat());
+        mBandwidthKhz = in.readFloat();
+        mBandwidthRangeKhz = new Range<>(in.readFloat(), in.readFloat());
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeFloat(mBitrateKbps);
+        out.writeFloat(mBitrateRangeKbps.getLower());
+        out.writeFloat(mBitrateRangeKbps.getUpper());
+        out.writeFloat(mBandwidthKhz);
+        out.writeFloat(mBandwidthRangeKhz.getLower());
+        out.writeFloat(mBandwidthRangeKhz.getUpper());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<AudioCodecAttributes> CREATOR =
+            new Creator<AudioCodecAttributes>() {
+                @Override
+                public AudioCodecAttributes createFromParcel(Parcel in) {
+                    return new AudioCodecAttributes(in);
+                }
+
+                @Override
+                public AudioCodecAttributes[] newArray(int size) {
+                    return new AudioCodecAttributes[size];
+                }
+            };
+
+    /**
+     * @return the exact value of the audio codec bitrate in kbps.
+     */
+    public float getBitrateKbps() {
+        return mBitrateKbps;
+    }
+
+    /**
+     * @return the range of the audio codec bitrate in kbps
+     */
+    public @NonNull Range<Float> getBitrateRangeKbps() {
+        return mBitrateRangeKbps;
+    }
+
+    /**
+     * @return the exact value of the audio codec bandwidth in kHz.
+     */
+    public float getBandwidthKhz() {
+        return mBandwidthKhz;
+    }
+
+    /**
+     * @return the range of the audio codec bandwidth in kHz.
+     */
+    public @NonNull Range<Float> getBandwidthRangeKhz() {
+        return mBandwidthRangeKhz;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "{ bitrateKbps=" + mBitrateKbps
+                + ", bitrateRangeKbps=" + mBitrateRangeKbps
+                + ", bandwidthKhz=" + mBandwidthKhz
+                + ", bandwidthRangeKhz=" + mBandwidthRangeKhz + " }";
+    }
+}
diff --git a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
index 2792f79..d924bae 100644
--- a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
+++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
@@ -17,6 +17,7 @@
 package android.telephony.ims;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
@@ -90,6 +91,9 @@
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     public int mAudioDirection;
+    // Audio codec attributes
+    private AudioCodecAttributes mAudioCodecAttributes;
+
     // Video related information
     /** @hide */
     public int mVideoQuality;
@@ -191,6 +195,7 @@
     public void copyFrom(ImsStreamMediaProfile profile) {
         mAudioQuality = profile.mAudioQuality;
         mAudioDirection = profile.mAudioDirection;
+        mAudioCodecAttributes = profile.mAudioCodecAttributes;
         mVideoQuality = profile.mVideoQuality;
         mVideoDirection = profile.mVideoDirection;
         mRttMode = profile.mRttMode;
@@ -199,12 +204,13 @@
     @NonNull
     @Override
     public String toString() {
-        return "{ audioQuality=" + mAudioQuality +
-                ", audioDirection=" + mAudioDirection +
-                ", videoQuality=" + mVideoQuality +
-                ", videoDirection=" + mVideoDirection +
-                ", rttMode=" + mRttMode +
-                ", hasRttAudioSpeech=" + mIsReceivingRttAudio + " }";
+        return "{ audioQuality=" + mAudioQuality
+                + ", audioDirection=" + mAudioDirection
+                + ", audioCodecAttribute=" + mAudioCodecAttributes
+                + ", videoQuality=" + mVideoQuality
+                + ", videoDirection=" + mVideoDirection
+                + ", rttMode=" + mRttMode
+                + ", hasRttAudioSpeech=" + mIsReceivingRttAudio + " }";
     }
 
     @Override
@@ -216,6 +222,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(mAudioQuality);
         out.writeInt(mAudioDirection);
+        out.writeTypedObject(mAudioCodecAttributes, flags);
         out.writeInt(mVideoQuality);
         out.writeInt(mVideoDirection);
         out.writeInt(mRttMode);
@@ -225,6 +232,7 @@
     private void readFromParcel(Parcel in) {
         mAudioQuality = in.readInt();
         mAudioDirection = in.readInt();
+        mAudioCodecAttributes = in.readTypedObject(AudioCodecAttributes.CREATOR);
         mVideoQuality = in.readInt();
         mVideoDirection = in.readInt();
         mRttMode = in.readInt();
@@ -275,6 +283,23 @@
         return mAudioDirection;
     }
 
+    /**
+     * Get the audio codec attributes {@link AudioCodecAttributes} which may be {@code null} if
+     * ImsService doesn't support this information.
+     * @return audio codec attributes
+     */
+    public @Nullable AudioCodecAttributes getAudioCodecAttributes() {
+        return mAudioCodecAttributes;
+    }
+
+    /**
+     * Set the audio codec attributes {@link AudioCodecAttributes} which includes bitrate and
+     * bandwidth information.
+     */
+    public void setAudioCodecAttributes(@NonNull AudioCodecAttributes audioCodecAttributes) {
+        mAudioCodecAttributes = audioCodecAttributes;
+    }
+
     public int getVideoQuality() {
         return mVideoQuality;
     }
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index 1adbc2d..322bbff 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,7 +32,7 @@
 
 
 
-    // Code below generated by codegen v1.0.18.
+    // Code below generated by codegen v1.0.19.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -98,8 +98,8 @@
     };
 
     @DataClass.Generated(
-            time = 1603836848866L,
-            codegenVersion = "1.0.18",
+            time = 1604435620553L,
+            codegenVersion = "1.0.19",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
             inputSignatures = "private  int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
     @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
index a4fdcd1..a8ae72d 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,7 +46,7 @@
 
 
 
-    // Code below generated by codegen v1.0.18.
+    // Code below generated by codegen v1.0.19.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -120,8 +120,8 @@
     };
 
     @DataClass.Generated(
-            time = 1603836849753L,
-            codegenVersion = "1.0.18",
+            time = 1604435621500L,
+            codegenVersion = "1.0.19",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
             inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
     @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
index f0d728e..ca4278d 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -54,7 +54,7 @@
 
 
 
-    // Code below generated by codegen v1.0.18.
+    // Code below generated by codegen v1.0.19.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -412,8 +412,8 @@
     }
 
     @DataClass.Generated(
-            time = 1603836847927L,
-            codegenVersion = "1.0.18",
+            time = 1604435619612L,
+            codegenVersion = "1.0.19",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
             inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings({\"WeakerAccess\"}) @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
     @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index a3f458b..ce1e043 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -344,7 +344,7 @@
 
 
 
-    // Code below generated by codegen v1.0.18.
+    // Code below generated by codegen v1.0.19.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -1874,8 +1874,8 @@
     }
 
     @DataClass.Generated(
-            time = 1603836845952L,
-            codegenVersion = "1.0.18",
+            time = 1604435617581L,
+            codegenVersion = "1.0.19",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
             inputSignatures = "public static final  java.lang.String STATE_NAME_UNDEFINED\npublic static final  java.lang.String STATE_NAME_ON\npublic static final  java.lang.String STATE_NAME_OFF\npublic static final  int STATE_ON\npublic static final  int STATE_OFF\npublic static final  int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate  int mNum\nprivate  int mNum2\nprivate  int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient  android.net.LinkAddress[] mLinkAddresses6\ntransient  int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static  java.lang.String defaultName4()\nprivate  int[] lazyInitTmpStorage()\npublic  android.net.LinkAddress[] getLinkAddresses4()\nprivate  boolean patternEquals(java.util.regex.Pattern)\nprivate  int patternHashCode()\nprivate  void onConstructed()\npublic  void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
     @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index e356704..5bbbf41 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,7 +85,7 @@
 
 
 
-    // Code below generated by codegen v1.0.18.
+    // Code below generated by codegen v1.0.19.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -253,8 +253,8 @@
     }
 
     @DataClass.Generated(
-            time = 1603836846970L,
-            codegenVersion = "1.0.18",
+            time = 1604435618584L,
+            codegenVersion = "1.0.19",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
             inputSignatures = "  long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n  long creationTimestamp\nprivate static  java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate  void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\nabstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic  com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract  com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic  com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
index 07ec31d..c762164 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
@@ -36,7 +36,7 @@
 
 
 
-        // Code below generated by codegen v1.0.18.
+        // Code below generated by codegen v1.0.19.
         //
         // DO NOT MODIFY!
         // CHECKSTYLE:OFF Generated code
@@ -135,8 +135,8 @@
         };
 
         @DataClass.Generated(
-                time = 1603836851627L,
-                codegenVersion = "1.0.18",
+                time = 1604435623368L,
+                codegenVersion = "1.0.19",
                 sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
                 inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
         @Deprecated
@@ -160,7 +160,7 @@
 
 
 
-            // Code below generated by codegen v1.0.18.
+            // Code below generated by codegen v1.0.19.
             //
             // DO NOT MODIFY!
             // CHECKSTYLE:OFF Generated code
@@ -259,8 +259,8 @@
             };
 
             @DataClass.Generated(
-                    time = 1603836851635L,
-                    codegenVersion = "1.0.18",
+                    time = 1604435623377L,
+                    codegenVersion = "1.0.19",
                     sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
                     inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
             @Deprecated
@@ -274,7 +274,7 @@
 
 
 
-        // Code below generated by codegen v1.0.18.
+        // Code below generated by codegen v1.0.19.
         //
         // DO NOT MODIFY!
         // CHECKSTYLE:OFF Generated code
@@ -373,8 +373,8 @@
         };
 
         @DataClass.Generated(
-                time = 1603836851640L,
-                codegenVersion = "1.0.18",
+                time = 1604435623381L,
+                codegenVersion = "1.0.19",
                 sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
                 inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
         @Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index 5cbc6b3..0813dbe 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -59,7 +59,7 @@
 
 
 
-    // Code below generated by codegen v1.0.18.
+    // Code below generated by codegen v1.0.19.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -84,8 +84,8 @@
     }
 
     @DataClass.Generated(
-            time = 1603836850677L,
-            codegenVersion = "1.0.18",
+            time = 1604435622426L,
+            codegenVersion = "1.0.19",
             sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
             inputSignatures = "private @android.annotation.Nullable java.util.List<java.util.Set<?>> mUsesWildcards\npublic @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
     @Deprecated
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 6b974ff..db0eed8 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -45,6 +45,15 @@
     }
 }
 
+fun WmAssertion.visibleWindowsShownMoreThanOneConsecutiveEntry(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("visibleWindowShownMoreThanOneConsecutiveEntry", bugId, enabled) {
+        this.visibleWindowsShownMoreThanOneConsecutiveEntry()
+    }
+}
+
 @JvmOverloads
 fun LayersAssertion.noUncoveredRegions(
     beginRotation: Int,
@@ -159,6 +168,15 @@
     }
 }
 
+fun LayersAssertion.visibleLayersShownMoreThanOneConsecutiveEntry(
+    bugId: Int = 0,
+    enabled: Boolean = bugId == 0
+) {
+    all("visibleLayersShownMoreThanOneConsecutiveEntry", bugId, enabled) {
+        this.visibleLayersShownMoreThanOneConsecutiveEntry()
+    }
+}
+
 fun EventLogAssertion.focusChanges(
     vararg windows: String,
     bugId: Int = 0,
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
index d31c4ba..72efdb1 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/CommonAssertions.kt
@@ -43,12 +43,12 @@
     }
 }
 
-fun LayersAssertion.wallpaperLayerBecomesInvisible(
+fun LayersAssertion.appLayerReplacesWallpaperLayer(
     testApp: IAppHelper,
     bugId: Int = 0,
     enabled: Boolean = bugId == 0
 ) {
-    all("wallpaperLayerBecomesInvisible", bugId, enabled) {
+    all("appLayerReplacesWallpaperLayer", bugId, enabled) {
         this.showsLayer("Wallpaper")
                 .then()
                 .replaceVisibleLayer("Wallpaper", testApp.getPackage())
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
index ad23d9f..1f03c4d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt
@@ -38,6 +38,8 @@
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -87,6 +89,7 @@
                         windowManagerTrace {
                             navBarWindowIsAlwaysVisible()
                             statusBarWindowIsAlwaysVisible()
+                            visibleWindowsShownMoreThanOneConsecutiveEntry()
 
                             appWindowReplacesLauncherAsTopWindow(testApp)
                             wallpaperWindowBecomesInvisible()
@@ -102,8 +105,10 @@
                                 configuration.endRotation)
                             navBarLayerIsAlwaysVisible(enabled = false)
                             statusBarLayerIsAlwaysVisible(enabled = false)
+                            visibleLayersShownMoreThanOneConsecutiveEntry(
+                                    enabled = Surface.ROTATION_0 == configuration.endRotation)
 
-                            wallpaperLayerBecomesInvisible(testApp)
+                            appLayerReplacesWallpaperLayer(testApp)
                         }
 
                         eventLog {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
new file mode 100644
index 0000000..1833874
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.server.wm.flicker.launch
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.server.wm.flicker.Flicker
+import com.android.server.wm.flicker.FlickerTestRunnerFactory
+import com.android.server.wm.flicker.FlickerTestRunner
+import com.android.server.wm.flicker.helpers.StandardAppHelper
+import com.android.server.wm.flicker.helpers.reopenAppFromOverview
+import com.android.server.wm.flicker.helpers.hasWindow
+import com.android.server.wm.flicker.helpers.buildTestTag
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.repetitions
+import com.android.server.wm.flicker.startRotation
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Launch an app from the recents app view (the overview)
+ * To run this test: `atest FlickerTests:OpenAppFromOverviewTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenAppFromOverviewTest(
+    testName: String,
+    flickerSpec: Flicker
+) : FlickerTestRunner(testName, flickerSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<Array<Any>> {
+            val instrumentation = InstrumentationRegistry.getInstrumentation()
+            val testApp = StandardAppHelper(instrumentation,
+                    "com.android.server.wm.flicker.testapp", "SimpleApp")
+            return FlickerTestRunnerFactory(instrumentation, repetitions = 10)
+                    .buildTest { configuration ->
+                        withTag { buildTestTag("openAppFromOverview", testApp, configuration) }
+                        repeat { configuration.repetitions }
+                        setup {
+                            test {
+                                device.wakeUpAndGoToHomeScreen()
+                                testApp.open()
+                            }
+                            eachRun {
+                                device.pressHome()
+                                device.pressRecentApps()
+                                this.setRotation(configuration.startRotation)
+                            }
+                        }
+                        transitions {
+                            device.reopenAppFromOverview()
+                            device.hasWindow(testApp.getPackage())
+                        }
+                        teardown {
+                            test {
+                                testApp.exit()
+                                this.setRotation(Surface.ROTATION_0)
+                            }
+                        }
+                    }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
index 5886a61..9b4223a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt
@@ -38,6 +38,8 @@
 import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
 import com.android.server.wm.flicker.statusBarLayerRotatesScales
 import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
@@ -91,9 +93,10 @@
                         windowManagerTrace {
                             navBarWindowIsAlwaysVisible()
                             statusBarWindowIsAlwaysVisible()
+                            visibleWindowsShownMoreThanOneConsecutiveEntry()
 
                             appWindowReplacesLauncherAsTopWindow(testApp)
-                            wallpaperWindowBecomesInvisible(enabled = false)
+                            wallpaperWindowBecomesInvisible()
                         }
 
                         layersTrace {
@@ -106,8 +109,10 @@
                                 configuration.endRotation)
                             navBarLayerIsAlwaysVisible(enabled = false)
                             statusBarLayerIsAlwaysVisible(enabled = false)
+                            visibleLayersShownMoreThanOneConsecutiveEntry(
+                                    enabled = Surface.ROTATION_0 == configuration.endRotation)
 
-                            wallpaperLayerBecomesInvisible(testApp)
+                            appLayerReplacesWallpaperLayer(testApp)
                         }
 
                         eventLog {
diff --git a/tests/vcn/Android.bp b/tests/vcn/Android.bp
new file mode 100644
index 0000000..f967bf0
--- /dev/null
+++ b/tests/vcn/Android.bp
@@ -0,0 +1,27 @@
+//########################################################################
+// Build FrameworksVcnTests package
+//########################################################################
+
+android_test {
+    name: "FrameworksVcnTests",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.kt",
+    ],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    certificate: "platform",
+    static_libs: [
+        "androidx.test.rules",
+        "frameworks-base-testutils",
+        "framework-protos",
+        "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "services.core",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+}
diff --git a/tests/vcn/AndroidManifest.xml b/tests/vcn/AndroidManifest.xml
new file mode 100644
index 0000000..2ad9aac
--- /dev/null
+++ b/tests/vcn/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.tests.vcn">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.tests.vcn"
+        android:label="Frameworks VCN Tests" />
+</manifest>
diff --git a/tests/vcn/AndroidTest.xml b/tests/vcn/AndroidTest.xml
new file mode 100644
index 0000000..dc521fd
--- /dev/null
+++ b/tests/vcn/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs VCN Tests.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="FrameworksVcnTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="FrameworksVcnTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.tests.vcn" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/tests/vcn/TEST_MAPPING b/tests/vcn/TEST_MAPPING
new file mode 100644
index 0000000..54fa411
--- /dev/null
+++ b/tests/vcn/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "FrameworksVcnTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/ClassInfo.kt b/tools/codegen/src/com/android/codegen/ClassInfo.kt
index bf95a2e..056898c 100644
--- a/tools/codegen/src/com/android/codegen/ClassInfo.kt
+++ b/tools/codegen/src/com/android/codegen/ClassInfo.kt
@@ -1,12 +1,14 @@
 package com.android.codegen
 
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.TypeDeclaration
 
 open class ClassInfo(val classAst: ClassOrInterfaceDeclaration, val fileInfo: FileInfo) {
 
     val fileAst = fileInfo.fileAst
 
     val nestedClasses = classAst.members.filterIsInstance<ClassOrInterfaceDeclaration>()
+    val nestedTypes = classAst.members.filterIsInstance<TypeDeclaration<*>>()
 
     val superInterfaces = classAst.implementedTypes.map { it.asString() }
     val superClass = classAst.extendedTypes.getOrNull(0)
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index 69ff18d..1aea575 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -128,7 +128,7 @@
 
     if (classAst.nameAsString == className) return thisPackagePrefix + classAst.nameAsString
 
-    nestedClasses.find {
+    nestedTypes.find {
         it.nameAsString == className
     }?.let { return thisClassPrefix + it.nameAsString }
 
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index ca658a9..147f18c 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
 package com.android.codegen
 
 const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.18"
+const val CODEGEN_VERSION = "1.0.19"
 
 const val CANONICAL_BUILDER_CLASS = "Builder"
 const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/validatekeymaps/Android.bp b/tools/validatekeymaps/Android.bp
index 2759e29..15b8b41 100644
--- a/tools/validatekeymaps/Android.bp
+++ b/tools/validatekeymaps/Android.bp
@@ -16,18 +16,25 @@
 
     static_libs: [
         "libbase",
-        "libbinder",
         "libinput",
         "libutils",
         "libcutils",
         "liblog",
         "libui-types",
     ],
+    target: {
+        linux_glibc: {
+            static_libs: [
+                // libbinder is only available for linux
+                "libbinder",
+            ],
+        },
+    },
 
     // This tool is prebuilt if we're doing an app-only build.
     product_variables: {
         unbundled_build: {
-          enabled: false,
+            enabled: false,
         },
     },
 }
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
index 0af6266..3865076 100644
--- a/tools/validatekeymaps/Main.cpp
+++ b/tools/validatekeymaps/Main.cpp
@@ -105,8 +105,8 @@
     }
 
     case FILETYPE_KEYCHARACTERMAP: {
-        base::Result<std::shared_ptr<KeyCharacterMap>> ret = KeyCharacterMap::load(filename,
-                KeyCharacterMap::FORMAT_ANY);
+        base::Result<std::shared_ptr<KeyCharacterMap>> ret =
+                KeyCharacterMap::load(filename, KeyCharacterMap::Format::ANY);
         if (!ret) {
             error("Error %s parsing key character map file.\n\n", ret.error().message().c_str());
             return false;
diff --git a/wifi/api/current.txt b/wifi/api/current.txt
index d5ef703..5c4e615 100644
--- a/wifi/api/current.txt
+++ b/wifi/api/current.txt
@@ -105,6 +105,7 @@
     field @Deprecated public String FQDN;
     field @Deprecated public static final int SECURITY_TYPE_EAP = 3; // 0x3
     field @Deprecated public static final int SECURITY_TYPE_EAP_SUITE_B = 5; // 0x5
+    field @Deprecated public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9; // 0x9
     field @Deprecated public static final int SECURITY_TYPE_OPEN = 0; // 0x0
     field @Deprecated public static final int SECURITY_TYPE_OWE = 6; // 0x6
     field @Deprecated public static final int SECURITY_TYPE_PSK = 2; // 0x2
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 9298c1e..34a6938 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -447,6 +447,8 @@
     public static final int SECURITY_TYPE_WAPI_PSK = 7;
     /** Security type for a WAPI Certificate network. */
     public static final int SECURITY_TYPE_WAPI_CERT = 8;
+    /** Security type for a WPA3-Enterprise network. */
+    public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9;
 
     /**
      * Security types we support.
@@ -462,7 +464,8 @@
             SECURITY_TYPE_EAP_SUITE_B,
             SECURITY_TYPE_OWE,
             SECURITY_TYPE_WAPI_PSK,
-            SECURITY_TYPE_WAPI_CERT
+            SECURITY_TYPE_WAPI_CERT,
+            SECURITY_TYPE_EAP_WPA3_ENTERPRISE,
     })
     public @interface SecurityType {}
 
@@ -478,8 +481,9 @@
      * {@link #SECURITY_TYPE_SAE},
      * {@link #SECURITY_TYPE_EAP_SUITE_B},
      * {@link #SECURITY_TYPE_OWE},
-     * {@link #SECURITY_TYPE_WAPI_PSK}, or
-     * {@link #SECURITY_TYPE_WAPI_CERT}
+     * {@link #SECURITY_TYPE_WAPI_PSK},
+     * {@link #SECURITY_TYPE_WAPI_CERT},
+     * {@link #SECURITY_TYPE_EAP_WPA3_ENTERPRISE}
      */
     public void setSecurityParams(@SecurityType int securityType) {
         // Clear all the bitsets.
@@ -555,6 +559,16 @@
                 allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.SMS4);
                 allowedGroupCiphers.set(WifiConfiguration.GroupCipher.SMS4);
                 break;
+            case SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
+                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
+                allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
+                allowedProtocols.set(WifiConfiguration.Protocol.RSN);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+                allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
+                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
+                allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
+                requirePmf = true;
+                break;
             default:
                 throw new IllegalArgumentException("unknown security type " + securityType);
         }
diff --git a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
index 35853c0..f5ffd93 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSpecifier.java
@@ -373,14 +373,8 @@
                     configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
                 } else {
                     // WPA3-Enterprise
-                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
-                    configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-                    configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-                    configuration.allowedPairwiseCiphers.set(
-                            WifiConfiguration.PairwiseCipher.GCMP_256);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-                    configuration.requirePmf = true;
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
                 }
                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
             } else if (mIsEnhancedOpen) { // OWE network
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index dc6ec90..dc51897 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -802,14 +802,8 @@
                     configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
                 } else {
                     // WPA3-Enterprise
-                    configuration.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
-                    configuration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
-                    configuration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
-                    configuration.allowedPairwiseCiphers.set(
-                            WifiConfiguration.PairwiseCipher.GCMP_256);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
-                    configuration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
-                    configuration.requirePmf = true;
+                    configuration.setSecurityParams(
+                            WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
                 }
                 configuration.enterpriseConfig = mWpa3EnterpriseConfig;
             } else if (mIsEnhancedOpen) { // OWE network
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index f09c37d..0a80dc2 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -18,6 +18,7 @@
 
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OPEN;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_PSK;
@@ -581,6 +582,26 @@
     }
 
     /**
+     * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+     * {@link WifiConfiguration} object correctly for WPA3 Enterprise security type.
+     * @throws Exception
+     */
+    @Test
+    public void testSetSecurityParamsForWpa3Enterprise() throws Exception {
+        WifiConfiguration config = new WifiConfiguration();
+
+        config.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP));
+        assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.IEEE8021X));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+        assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+        assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256));
+        assertTrue(config.requirePmf);
+    }
+
+    /**
      * Test that the NetworkSelectionStatus Builder returns the same values that was set, and that
      * calling build multiple times returns different instances.
      */
@@ -641,6 +662,9 @@
         configuration.setSecurityParams(SECURITY_TYPE_EAP);
         assertFalse(configuration.needsPreSharedKey());
 
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        assertFalse(configuration.needsPreSharedKey());
+
         configuration.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
         assertFalse(configuration.needsPreSharedKey());
     }
@@ -667,6 +691,9 @@
         configuration.setSecurityParams(SECURITY_TYPE_EAP);
         assertEquals(KeyMgmt.WPA_EAP, configuration.getAuthType());
 
+        configuration.setSecurityParams(SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
+        assertEquals(KeyMgmt.WPA_EAP, configuration.getAuthType());
+
         configuration.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
         assertEquals(KeyMgmt.SUITE_B_192, configuration.getAuthType());